@aigne/afs-synology 1.11.0-beta.12

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.
@@ -0,0 +1,1687 @@
1
+ import { AFSSynologyConfigSchema, KINDS } from "./types.mjs";
2
+ import { SynologyClient } from "./client.mjs";
3
+ import { formatBackupTaskInfo, formatBytes, formatContainerInfo, formatDiskInfo, formatHardwareInfo, formatImageInfo, formatPackageInfo, formatPoolInfo, formatShareInfo, formatSystemStatus, formatSystemUtilization, formatVolumeInfo } from "./formatters.mjs";
4
+ import { __decorate } from "./_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs";
5
+ import { AFSBaseProvider, AFSError, AFSNotFoundError, Actions, Explain, List, Meta, Read, Search, Stat } from "@aigne/afs";
6
+ import { z } from "zod";
7
+ import { joinURL } from "ufo";
8
+
9
+ //#region src/synology-afs.ts
10
+ /**
11
+ * AFSSynology - Synology NAS AFS Provider
12
+ *
13
+ * Maps Synology DSM management capabilities to AFS filesystem interface.
14
+ * File read/write goes through afs-fs (NFS/SMB); this provider focuses on:
15
+ * - System health monitoring
16
+ * - Storage status (disks, pools, volumes)
17
+ * - Docker container management (including start/stop/restart)
18
+ * - Backup monitoring and task triggering
19
+ * - Package management (start/stop)
20
+ */
21
+ /**
22
+ * Synology NAS AFS Provider
23
+ *
24
+ * Readwrite mode: supports exec operations (container start/stop/restart,
25
+ * backup trigger, package start/stop). Write and delete are never supported.
26
+ */
27
+ var AFSSynology = class AFSSynology extends AFSBaseProvider {
28
+ static securityProfiles() {
29
+ return {
30
+ admin: {
31
+ actionPolicy: "full",
32
+ sensitivity: "full"
33
+ },
34
+ user: {
35
+ actionPolicy: "standard",
36
+ sensitiveFields: ["serial_number"],
37
+ sensitivity: "redacted"
38
+ },
39
+ kid: {
40
+ actionPolicy: "safe",
41
+ accessMode: "readonly",
42
+ blockedActions: ["stop", "restart"],
43
+ sensitiveFields: ["serial_number"],
44
+ sensitivity: "redacted"
45
+ },
46
+ guest: {
47
+ actionPolicy: "safe",
48
+ accessMode: "readonly",
49
+ sensitiveFields: ["serial_number"],
50
+ sensitivity: "redacted"
51
+ }
52
+ };
53
+ }
54
+ name;
55
+ description;
56
+ accessMode = "readwrite";
57
+ client;
58
+ systemInfo = null;
59
+ utilization = null;
60
+ disks = null;
61
+ pools = null;
62
+ volumes = null;
63
+ containers = null;
64
+ images = null;
65
+ shares = null;
66
+ packages = null;
67
+ backupTasks = null;
68
+ constructor(config) {
69
+ super();
70
+ const { uri: _uri, ...cleanConfig } = config;
71
+ const validated = AFSSynologyConfigSchema.parse(cleanConfig);
72
+ if (!validated.url.startsWith("http://") && !validated.url.startsWith("https://")) throw new AFSError("URL must start with http:// or https://", "AFS_INVALID_PARAMETER");
73
+ this.name = validated.name || "synology";
74
+ this.description = validated.description || `Synology NAS at ${validated.url}`;
75
+ this.client = new SynologyClient({
76
+ url: validated.url,
77
+ account: validated.account,
78
+ password: validated.password,
79
+ otpCode: validated.otpCode,
80
+ deviceId: validated.deviceId,
81
+ verifySsl: validated.verifySsl
82
+ });
83
+ }
84
+ static schema() {
85
+ return AFSSynologyConfigSchema;
86
+ }
87
+ static manifest() {
88
+ return {
89
+ name: "synology",
90
+ description: "Synology NAS management — system health, storage status, Docker containers, backup monitoring.\n- Monitor CPU, RAM, disk health, temperature\n- View Docker containers, images, networks\n- Start/stop/restart Docker containers\n- Check Hyper Backup task status and trigger backups\n- Start/stop DSM packages\n- Path structure: `/system/`, `/storage/`, `/docker/`, `/shares/`, `/packages/`, `/backup/`",
91
+ uriTemplate: "synology://{host}",
92
+ category: "storage",
93
+ schema: z.object({ host: z.string().optional() }),
94
+ tags: [
95
+ "synology",
96
+ "nas",
97
+ "docker",
98
+ "storage",
99
+ "backup"
100
+ ],
101
+ capabilityTags: [
102
+ "read-write",
103
+ "crud",
104
+ "search",
105
+ "auth:token",
106
+ "remote",
107
+ "http",
108
+ "on-premise"
109
+ ],
110
+ security: {
111
+ riskLevel: "external",
112
+ resourceAccess: ["internet", "local-network"],
113
+ requires: ["docker"],
114
+ dataSensitivity: ["system-config", "media"],
115
+ notes: ["Manages NAS system — can start/stop Docker containers and DSM packages"]
116
+ },
117
+ capabilities: {
118
+ network: { egress: true },
119
+ secrets: ["synology/password"]
120
+ }
121
+ };
122
+ }
123
+ static treeSchema() {
124
+ return {
125
+ operations: [
126
+ "list",
127
+ "read",
128
+ "search",
129
+ "exec",
130
+ "stat",
131
+ "explain"
132
+ ],
133
+ tree: {
134
+ "/": { kind: "nas:root" },
135
+ "/system": {
136
+ kind: "nas:system-group",
137
+ operations: ["list"]
138
+ },
139
+ "/system/status": {
140
+ kind: "nas:system-info",
141
+ operations: ["read"]
142
+ },
143
+ "/system/utilization": {
144
+ kind: "nas:system-info",
145
+ operations: ["read"]
146
+ },
147
+ "/system/hardware": {
148
+ kind: "nas:system-info",
149
+ operations: ["read"]
150
+ },
151
+ "/storage": {
152
+ kind: "nas:storage-group",
153
+ operations: ["list"]
154
+ },
155
+ "/storage/disks": {
156
+ kind: "afs:directory",
157
+ operations: ["list"]
158
+ },
159
+ "/storage/disks/{diskId}": {
160
+ kind: "nas:disk",
161
+ operations: ["read"]
162
+ },
163
+ "/storage/pools": {
164
+ kind: "afs:directory",
165
+ operations: ["list"]
166
+ },
167
+ "/storage/pools/{poolId}": {
168
+ kind: "nas:storage-pool",
169
+ operations: ["read"]
170
+ },
171
+ "/storage/volumes": {
172
+ kind: "afs:directory",
173
+ operations: ["list"]
174
+ },
175
+ "/storage/volumes/{volumeId}": {
176
+ kind: "nas:volume",
177
+ operations: ["read"]
178
+ },
179
+ "/docker": {
180
+ kind: "nas:docker-group",
181
+ operations: ["list"]
182
+ },
183
+ "/docker/containers": {
184
+ kind: "afs:directory",
185
+ operations: ["list"]
186
+ },
187
+ "/docker/containers/{name}": {
188
+ kind: "nas:container",
189
+ operations: ["read", "exec"],
190
+ actions: [
191
+ "start",
192
+ "stop",
193
+ "restart"
194
+ ],
195
+ destructive: ["stop"]
196
+ },
197
+ "/docker/images": {
198
+ kind: "afs:directory",
199
+ operations: ["list"]
200
+ },
201
+ "/docker/images/{name}": {
202
+ kind: "nas:docker-image",
203
+ operations: ["read"]
204
+ },
205
+ "/docker/networks": {
206
+ kind: "afs:directory",
207
+ operations: ["list"]
208
+ },
209
+ "/docker/projects": {
210
+ kind: "afs:directory",
211
+ operations: ["list"]
212
+ },
213
+ "/shares": {
214
+ kind: "afs:directory",
215
+ operations: ["list"]
216
+ },
217
+ "/shares/{name}": {
218
+ kind: "nas:shared-folder",
219
+ operations: ["read"]
220
+ },
221
+ "/packages": {
222
+ kind: "afs:directory",
223
+ operations: ["list"]
224
+ },
225
+ "/packages/{name}": {
226
+ kind: "nas:package",
227
+ operations: ["read", "exec"],
228
+ actions: ["start", "stop"]
229
+ },
230
+ "/backup": {
231
+ kind: "afs:directory",
232
+ operations: ["list"]
233
+ },
234
+ "/backup/tasks": {
235
+ kind: "afs:directory",
236
+ operations: ["list"]
237
+ },
238
+ "/backup/tasks/{taskId}": {
239
+ kind: "nas:backup-task",
240
+ operations: ["read", "exec"],
241
+ actions: ["run"]
242
+ }
243
+ },
244
+ auth: {
245
+ type: "custom",
246
+ env: [
247
+ "SYNOLOGY_URL",
248
+ "SYNOLOGY_ACCOUNT",
249
+ "SYNOLOGY_PASSWORD"
250
+ ]
251
+ },
252
+ bestFor: [
253
+ "NAS monitoring",
254
+ "Docker management",
255
+ "backup monitoring"
256
+ ],
257
+ notFor: ["file read/write", "database queries"]
258
+ };
259
+ }
260
+ static async load({ config } = {}) {
261
+ return new AFSSynology(AFSSynologyConfigSchema.parse(config ?? {}));
262
+ }
263
+ async getSystemInfo() {
264
+ if (!this.systemInfo) this.systemInfo = await this.client.request({
265
+ api: "SYNO.Core.System",
266
+ method: "info",
267
+ version: 1
268
+ });
269
+ return this.systemInfo;
270
+ }
271
+ async getUtilization() {
272
+ if (!this.utilization) this.utilization = await this.client.request({
273
+ api: "SYNO.Core.System.Utilization",
274
+ method: "get",
275
+ version: 1
276
+ });
277
+ return this.utilization;
278
+ }
279
+ async getDisks() {
280
+ if (!this.disks) this.disks = (await this.client.request({
281
+ api: "SYNO.Storage.CGI.Storage",
282
+ method: "load_info",
283
+ version: 1
284
+ })).disks || [];
285
+ return this.disks;
286
+ }
287
+ async getPools() {
288
+ if (!this.pools) this.pools = (await this.client.request({
289
+ api: "SYNO.Storage.CGI.Storage",
290
+ method: "load_info",
291
+ version: 1
292
+ })).storagePools || [];
293
+ return this.pools;
294
+ }
295
+ async getVolumes() {
296
+ if (!this.volumes) this.volumes = (await this.client.request({
297
+ api: "SYNO.Storage.CGI.Storage",
298
+ method: "load_info",
299
+ version: 1
300
+ })).volumes || [];
301
+ return this.volumes;
302
+ }
303
+ async getContainers() {
304
+ if (!this.containers) this.containers = (await this.client.request({
305
+ api: "SYNO.Docker.Container",
306
+ method: "list",
307
+ version: 1,
308
+ extra: {
309
+ limit: -1,
310
+ offset: 0,
311
+ type: "all"
312
+ }
313
+ })).containers || [];
314
+ return this.containers;
315
+ }
316
+ async getImages() {
317
+ if (!this.images) this.images = (await this.client.request({
318
+ api: "SYNO.Docker.Image",
319
+ method: "list",
320
+ version: 1,
321
+ extra: {
322
+ limit: -1,
323
+ offset: 0
324
+ }
325
+ })).images || [];
326
+ return this.images;
327
+ }
328
+ async getShares() {
329
+ if (!this.shares) this.shares = (await this.client.request({
330
+ api: "SYNO.Core.Share",
331
+ method: "list",
332
+ version: 1
333
+ })).shares || [];
334
+ return this.shares;
335
+ }
336
+ async getPackages() {
337
+ if (!this.packages) this.packages = (await this.client.request({
338
+ api: "SYNO.Core.Package",
339
+ method: "list",
340
+ version: 1
341
+ })).packages || [];
342
+ return this.packages;
343
+ }
344
+ async getBackupTasks() {
345
+ if (!this.backupTasks) this.backupTasks = (await this.client.request({
346
+ api: "SYNO.Backup.Task",
347
+ method: "list",
348
+ version: 1
349
+ })).tasks || [];
350
+ return this.backupTasks;
351
+ }
352
+ /** Sanitize image name for use as path segment (replace / with -) */
353
+ sanitizeImageName(repository, _tag) {
354
+ return `${repository.replace(/\//g, "-")}`;
355
+ }
356
+ /** Invalidate container cache after exec operations */
357
+ invalidateContainerCache() {
358
+ this.containers = null;
359
+ this.client.invalidateCache();
360
+ }
361
+ /** Invalidate package cache after exec operations */
362
+ invalidatePackageCache() {
363
+ this.packages = null;
364
+ }
365
+ /** Invalidate backup task cache after exec operations */
366
+ invalidateBackupTaskCache() {
367
+ this.backupTasks = null;
368
+ }
369
+ async readCapabilities(_ctx) {
370
+ return {
371
+ id: "/.meta/.capabilities",
372
+ path: "/.meta/.capabilities",
373
+ content: {
374
+ schemaVersion: 1,
375
+ provider: this.name,
376
+ description: this.description || "Synology NAS management provider",
377
+ tools: [],
378
+ actions: [],
379
+ operations: this.getOperationsDeclaration()
380
+ },
381
+ meta: { kind: "afs:capabilities" }
382
+ };
383
+ }
384
+ async metaRoot(_ctx) {
385
+ const info = await this.getSystemInfo();
386
+ return {
387
+ id: "",
388
+ path: "/.meta",
389
+ meta: {
390
+ kind: KINDS.NAS,
391
+ model: info.model,
392
+ version: info.version,
393
+ serial: info.serial,
394
+ childrenCount: 6,
395
+ description: `Synology ${info.model} running DSM ${info.version}`
396
+ }
397
+ };
398
+ }
399
+ async metaSystem(_ctx) {
400
+ return {
401
+ id: "system",
402
+ path: "/system/.meta",
403
+ meta: {
404
+ kind: KINDS.SYSTEM_GROUP,
405
+ childrenCount: 3,
406
+ description: "System health and resource monitoring"
407
+ }
408
+ };
409
+ }
410
+ async metaSystemItem(ctx) {
411
+ const { item } = ctx.params;
412
+ const descriptions = {
413
+ status: "System health status summary",
414
+ utilization: "CPU, RAM, disk I/O, network statistics",
415
+ hardware: "Temperature, fan speed, power status"
416
+ };
417
+ if (!descriptions[item]) throw new AFSNotFoundError(joinURL("/system", item));
418
+ return {
419
+ id: item,
420
+ path: joinURL("/system", item, ".meta"),
421
+ meta: {
422
+ kind: KINDS.SYSTEM_INFO,
423
+ childrenCount: 0,
424
+ description: descriptions[item]
425
+ }
426
+ };
427
+ }
428
+ async metaStorage(_ctx) {
429
+ return {
430
+ id: "storage",
431
+ path: "/storage/.meta",
432
+ meta: {
433
+ kind: KINDS.STORAGE_GROUP,
434
+ childrenCount: 3,
435
+ description: "Physical disks, storage pools, and volumes"
436
+ }
437
+ };
438
+ }
439
+ async metaDisks(_ctx) {
440
+ return {
441
+ id: "disks",
442
+ path: "/storage/disks/.meta",
443
+ meta: {
444
+ childrenCount: (await this.getDisks()).length,
445
+ description: "Physical disk drives"
446
+ }
447
+ };
448
+ }
449
+ async metaDisk(ctx) {
450
+ const disk = (await this.getDisks()).find((d) => d.id === ctx.params.diskId);
451
+ if (!disk) throw new AFSNotFoundError(joinURL("/storage/disks", ctx.params.diskId));
452
+ return {
453
+ id: disk.id,
454
+ path: joinURL("/storage/disks", disk.id, ".meta"),
455
+ meta: {
456
+ kind: KINDS.DISK,
457
+ id: disk.id,
458
+ childrenCount: 0,
459
+ description: `${disk.model} - ${formatBytes(disk.size_total)} - ${disk.smart_status || disk.status}`
460
+ }
461
+ };
462
+ }
463
+ async metaPools(_ctx) {
464
+ return {
465
+ id: "pools",
466
+ path: "/storage/pools/.meta",
467
+ meta: {
468
+ childrenCount: (await this.getPools()).length,
469
+ description: "RAID storage pools"
470
+ }
471
+ };
472
+ }
473
+ async metaPool(ctx) {
474
+ const pool = (await this.getPools()).find((p) => p.id === ctx.params.poolId);
475
+ if (!pool) throw new AFSNotFoundError(joinURL("/storage/pools", ctx.params.poolId));
476
+ return {
477
+ id: pool.id,
478
+ path: joinURL("/storage/pools", pool.id, ".meta"),
479
+ meta: {
480
+ kind: KINDS.STORAGE_POOL,
481
+ id: pool.id,
482
+ childrenCount: 0,
483
+ description: `${pool.raidType || "unknown"} pool - ${pool.status}`
484
+ }
485
+ };
486
+ }
487
+ async metaVolumes(_ctx) {
488
+ return {
489
+ id: "volumes",
490
+ path: "/storage/volumes/.meta",
491
+ meta: {
492
+ childrenCount: (await this.getVolumes()).length,
493
+ description: "Logical volumes"
494
+ }
495
+ };
496
+ }
497
+ async metaVolume(ctx) {
498
+ const volume = (await this.getVolumes()).find((v) => v.id === ctx.params.volumeId);
499
+ if (!volume) throw new AFSNotFoundError(joinURL("/storage/volumes", ctx.params.volumeId));
500
+ const usedPercent = Math.round(volume.size.used / volume.size.total * 100);
501
+ return {
502
+ id: volume.id,
503
+ path: joinURL("/storage/volumes", volume.id, ".meta"),
504
+ meta: {
505
+ kind: KINDS.VOLUME,
506
+ id: volume.id,
507
+ childrenCount: 0,
508
+ description: `${volume.fs_type} - ${usedPercent}% used`
509
+ }
510
+ };
511
+ }
512
+ async metaDocker(_ctx) {
513
+ return {
514
+ id: "docker",
515
+ path: "/docker/.meta",
516
+ meta: {
517
+ kind: KINDS.DOCKER_GROUP,
518
+ childrenCount: 4,
519
+ description: "Docker containers, images, networks, and projects"
520
+ }
521
+ };
522
+ }
523
+ async metaContainers(_ctx) {
524
+ return {
525
+ id: "containers",
526
+ path: "/docker/containers/.meta",
527
+ meta: {
528
+ childrenCount: -1,
529
+ description: "Running and stopped containers"
530
+ }
531
+ };
532
+ }
533
+ async metaContainer(ctx) {
534
+ const container = (await this.getContainers()).find((c) => c.name === ctx.params.containerName);
535
+ if (!container) throw new AFSNotFoundError(joinURL("/docker/containers", ctx.params.containerName));
536
+ return {
537
+ id: container.name,
538
+ path: joinURL("/docker/containers", container.name, ".meta"),
539
+ meta: {
540
+ kind: KINDS.CONTAINER,
541
+ id: container.name,
542
+ childrenCount: 0,
543
+ description: `${container.image} - ${container.status}`
544
+ }
545
+ };
546
+ }
547
+ async metaImages(_ctx) {
548
+ return {
549
+ id: "images",
550
+ path: "/docker/images/.meta",
551
+ meta: {
552
+ childrenCount: -1,
553
+ description: "Local Docker images"
554
+ }
555
+ };
556
+ }
557
+ async metaImage(ctx) {
558
+ const image = (await this.getImages()).find((i) => this.sanitizeImageName(i.repository, i.tag) === ctx.params.imageName);
559
+ if (!image) throw new AFSNotFoundError(joinURL("/docker/images", ctx.params.imageName));
560
+ return {
561
+ id: `${image.repository}:${image.tag}`,
562
+ path: joinURL("/docker/images", ctx.params.imageName, ".meta"),
563
+ meta: {
564
+ kind: KINDS.DOCKER_IMAGE,
565
+ id: `${image.repository}:${image.tag}`,
566
+ childrenCount: 0
567
+ }
568
+ };
569
+ }
570
+ async metaNetworks(_ctx) {
571
+ return {
572
+ id: "networks",
573
+ path: "/docker/networks/.meta",
574
+ meta: {
575
+ childrenCount: 0,
576
+ description: "Docker networks"
577
+ }
578
+ };
579
+ }
580
+ async metaProjects(_ctx) {
581
+ return {
582
+ id: "projects",
583
+ path: "/docker/projects/.meta",
584
+ meta: {
585
+ childrenCount: 0,
586
+ description: "Docker Compose projects (DSM 7.2+)"
587
+ }
588
+ };
589
+ }
590
+ async metaShares(_ctx) {
591
+ return {
592
+ id: "shares",
593
+ path: "/shares/.meta",
594
+ meta: {
595
+ childrenCount: (await this.getShares()).length,
596
+ description: "Shared folders"
597
+ }
598
+ };
599
+ }
600
+ async metaShare(ctx) {
601
+ const share = (await this.getShares()).find((s) => s.name === ctx.params.shareName);
602
+ if (!share) throw new AFSNotFoundError(joinURL("/shares", ctx.params.shareName));
603
+ return {
604
+ id: share.name,
605
+ path: joinURL("/shares", share.name, ".meta"),
606
+ meta: {
607
+ kind: KINDS.SHARED_FOLDER,
608
+ id: share.name,
609
+ childrenCount: 0,
610
+ description: share.path
611
+ }
612
+ };
613
+ }
614
+ async metaPackages(_ctx) {
615
+ return {
616
+ id: "packages",
617
+ path: "/packages/.meta",
618
+ meta: {
619
+ childrenCount: (await this.getPackages()).length,
620
+ description: "Installed DSM packages"
621
+ }
622
+ };
623
+ }
624
+ async metaPackage(ctx) {
625
+ const pkg = (await this.getPackages()).find((p) => p.id === ctx.params.packageName);
626
+ if (!pkg) throw new AFSNotFoundError(joinURL("/packages", ctx.params.packageName));
627
+ const updateStr = pkg.has_update ? " (update available)" : "";
628
+ return {
629
+ id: pkg.id,
630
+ path: joinURL("/packages", pkg.id, ".meta"),
631
+ meta: {
632
+ kind: KINDS.PACKAGE,
633
+ id: pkg.id,
634
+ childrenCount: 0,
635
+ description: `${pkg.name} v${pkg.version} - ${pkg.status}${updateStr}`
636
+ }
637
+ };
638
+ }
639
+ async metaBackup(_ctx) {
640
+ return {
641
+ id: "backup",
642
+ path: "/backup/.meta",
643
+ meta: {
644
+ childrenCount: 1,
645
+ description: "Backup management"
646
+ }
647
+ };
648
+ }
649
+ async metaBackupTasks(_ctx) {
650
+ return {
651
+ id: "tasks",
652
+ path: "/backup/tasks/.meta",
653
+ meta: {
654
+ childrenCount: (await this.getBackupTasks()).length,
655
+ description: "Hyper Backup tasks"
656
+ }
657
+ };
658
+ }
659
+ async metaBackupTask(ctx) {
660
+ const task = (await this.getBackupTasks()).find((t) => t.name === ctx.params.taskName);
661
+ if (!task) throw new AFSNotFoundError(joinURL("/backup/tasks", ctx.params.taskName));
662
+ const lastRunDate = task.last_run ? task.last_run.split("T")[0] : "never";
663
+ return {
664
+ id: task.name,
665
+ path: joinURL("/backup/tasks", task.name, ".meta"),
666
+ meta: {
667
+ kind: KINDS.BACKUP_TASK,
668
+ id: task.name,
669
+ childrenCount: 0,
670
+ description: `${task.status} - last run ${lastRunDate}`
671
+ }
672
+ };
673
+ }
674
+ async listRoot(_ctx) {
675
+ return { data: [
676
+ this.buildEntry("/system", { meta: {
677
+ kind: KINDS.SYSTEM_GROUP,
678
+ childrenCount: 3,
679
+ description: "System health and resource monitoring"
680
+ } }),
681
+ this.buildEntry("/storage", { meta: {
682
+ kind: KINDS.STORAGE_GROUP,
683
+ childrenCount: 3,
684
+ description: "Physical disks, storage pools, and volumes"
685
+ } }),
686
+ this.buildEntry("/docker", { meta: {
687
+ kind: KINDS.DOCKER_GROUP,
688
+ childrenCount: 4,
689
+ description: "Docker containers, images, networks, and projects"
690
+ } }),
691
+ this.buildEntry("/shares", { meta: {
692
+ kind: "nas:shared-folder-group",
693
+ childrenCount: -1,
694
+ description: "Shared folders"
695
+ } }),
696
+ this.buildEntry("/packages", { meta: {
697
+ kind: "nas:package-group",
698
+ childrenCount: -1,
699
+ description: "Installed DSM packages"
700
+ } }),
701
+ this.buildEntry("/backup", { meta: {
702
+ kind: "nas:backup-group",
703
+ childrenCount: 1,
704
+ description: "Backup management"
705
+ } })
706
+ ] };
707
+ }
708
+ async listSystem(_ctx) {
709
+ return { data: [
710
+ this.buildEntry("/system/status", { meta: {
711
+ kind: KINDS.SYSTEM_INFO,
712
+ childrenCount: 0,
713
+ description: "System health status summary"
714
+ } }),
715
+ this.buildEntry("/system/utilization", { meta: {
716
+ kind: KINDS.SYSTEM_INFO,
717
+ childrenCount: 0,
718
+ description: "CPU, RAM, disk I/O, network statistics"
719
+ } }),
720
+ this.buildEntry("/system/hardware", { meta: {
721
+ kind: KINDS.SYSTEM_INFO,
722
+ childrenCount: 0,
723
+ description: "Temperature, fan speed, power status"
724
+ } })
725
+ ] };
726
+ }
727
+ async listStorage(_ctx) {
728
+ const [disks, pools, volumes] = await Promise.all([
729
+ this.getDisks(),
730
+ this.getPools(),
731
+ this.getVolumes()
732
+ ]);
733
+ return { data: [
734
+ this.buildEntry("/storage/disks", { meta: {
735
+ childrenCount: disks.length,
736
+ description: "Physical disk drives"
737
+ } }),
738
+ this.buildEntry("/storage/pools", { meta: {
739
+ childrenCount: pools.length,
740
+ description: "RAID storage pools"
741
+ } }),
742
+ this.buildEntry("/storage/volumes", { meta: {
743
+ childrenCount: volumes.length,
744
+ description: "Logical volumes"
745
+ } })
746
+ ] };
747
+ }
748
+ async listDisks(_ctx) {
749
+ return { data: (await this.getDisks()).map((d) => this.buildEntry(joinURL("/storage/disks", d.id), { meta: {
750
+ kind: KINDS.DISK,
751
+ id: d.id,
752
+ childrenCount: 0,
753
+ description: `${d.model} - ${formatBytes(d.size_total)} - ${d.smart_status || d.status}`
754
+ } })) };
755
+ }
756
+ async listPools(_ctx) {
757
+ return { data: (await this.getPools()).map((p) => this.buildEntry(joinURL("/storage/pools", p.id), { meta: {
758
+ kind: KINDS.STORAGE_POOL,
759
+ id: p.id,
760
+ childrenCount: 0,
761
+ description: `${p.raidType || "unknown"} pool - ${p.status}`
762
+ } })) };
763
+ }
764
+ async listVolumes(_ctx) {
765
+ return { data: (await this.getVolumes()).map((v) => {
766
+ const usedPercent = Math.round(v.size.used / v.size.total * 100);
767
+ return this.buildEntry(joinURL("/storage/volumes", v.id), { meta: {
768
+ kind: KINDS.VOLUME,
769
+ id: v.id,
770
+ childrenCount: 0,
771
+ description: `${v.fs_type} - ${usedPercent}% used`
772
+ } });
773
+ }) };
774
+ }
775
+ async listDocker(_ctx) {
776
+ return { data: [
777
+ this.buildEntry("/docker/containers", { meta: {
778
+ childrenCount: -1,
779
+ description: "Running and stopped containers"
780
+ } }),
781
+ this.buildEntry("/docker/images", { meta: {
782
+ childrenCount: -1,
783
+ description: "Local Docker images"
784
+ } }),
785
+ this.buildEntry("/docker/networks", { meta: {
786
+ childrenCount: 0,
787
+ description: "Docker networks"
788
+ } }),
789
+ this.buildEntry("/docker/projects", { meta: {
790
+ childrenCount: 0,
791
+ description: "Docker Compose projects (DSM 7.2+)"
792
+ } })
793
+ ] };
794
+ }
795
+ async listContainers(_ctx) {
796
+ return { data: (await this.getContainers()).map((c) => this.buildEntry(joinURL("/docker/containers", c.name), { meta: {
797
+ kind: KINDS.CONTAINER,
798
+ id: c.name,
799
+ childrenCount: 0,
800
+ description: `${c.image} - ${c.status}`
801
+ } })) };
802
+ }
803
+ async listImages(_ctx) {
804
+ return { data: (await this.getImages()).map((i) => {
805
+ const safeName = this.sanitizeImageName(i.repository, i.tag);
806
+ return this.buildEntry(joinURL("/docker/images", safeName), { meta: {
807
+ kind: KINDS.DOCKER_IMAGE,
808
+ id: `${i.repository}:${i.tag}`,
809
+ childrenCount: 0
810
+ } });
811
+ }) };
812
+ }
813
+ async listNetworks(_ctx) {
814
+ return { data: [] };
815
+ }
816
+ async listProjects(_ctx) {
817
+ return { data: [] };
818
+ }
819
+ async listShares(_ctx) {
820
+ return { data: (await this.getShares()).map((s) => this.buildEntry(joinURL("/shares", s.name), { meta: {
821
+ kind: KINDS.SHARED_FOLDER,
822
+ id: s.name,
823
+ childrenCount: 0,
824
+ description: s.path
825
+ } })) };
826
+ }
827
+ async listPackages(_ctx) {
828
+ return { data: (await this.getPackages()).map((p) => {
829
+ const updateStr = p.has_update ? " (update available)" : "";
830
+ return this.buildEntry(joinURL("/packages", p.id), { meta: {
831
+ kind: KINDS.PACKAGE,
832
+ id: p.id,
833
+ childrenCount: 0,
834
+ description: `${p.name} v${p.version} - ${p.status}${updateStr}`
835
+ } });
836
+ }) };
837
+ }
838
+ async listBackup(_ctx) {
839
+ const tasks = await this.getBackupTasks();
840
+ return { data: [this.buildEntry("/backup/tasks", { meta: {
841
+ childrenCount: tasks.length,
842
+ description: "Hyper Backup tasks"
843
+ } })] };
844
+ }
845
+ async listBackupTasks(_ctx) {
846
+ return { data: (await this.getBackupTasks()).map((t) => {
847
+ const lastRunDate = t.last_run ? t.last_run.split("T")[0] : "never";
848
+ return this.buildEntry(joinURL("/backup/tasks", t.name), { meta: {
849
+ kind: KINDS.BACKUP_TASK,
850
+ id: t.name,
851
+ childrenCount: 0,
852
+ description: `${t.status} - last run ${lastRunDate}`
853
+ } });
854
+ }) };
855
+ }
856
+ async listSystemLeaf(_ctx) {
857
+ if (![
858
+ "status",
859
+ "utilization",
860
+ "hardware"
861
+ ].includes(_ctx.params.item)) throw new AFSNotFoundError(joinURL("/system", _ctx.params.item));
862
+ return { data: [] };
863
+ }
864
+ async listDiskLeaf(ctx) {
865
+ if (!(await this.getDisks()).find((d) => d.id === ctx.params.diskId)) throw new AFSNotFoundError(joinURL("/storage/disks", ctx.params.diskId));
866
+ return { data: [] };
867
+ }
868
+ async listPoolLeaf(ctx) {
869
+ if (!(await this.getPools()).find((p) => p.id === ctx.params.poolId)) throw new AFSNotFoundError(joinURL("/storage/pools", ctx.params.poolId));
870
+ return { data: [] };
871
+ }
872
+ async listVolumeLeaf(ctx) {
873
+ if (!(await this.getVolumes()).find((v) => v.id === ctx.params.volumeId)) throw new AFSNotFoundError(joinURL("/storage/volumes", ctx.params.volumeId));
874
+ return { data: [] };
875
+ }
876
+ async listContainerLeaf(ctx) {
877
+ if (!(await this.getContainers()).find((c) => c.name === ctx.params.containerName)) throw new AFSNotFoundError(joinURL("/docker/containers", ctx.params.containerName));
878
+ return { data: [] };
879
+ }
880
+ async listImageLeaf(ctx) {
881
+ if (!(await this.getImages()).find((i) => this.sanitizeImageName(i.repository, i.tag) === ctx.params.imageName)) throw new AFSNotFoundError(joinURL("/docker/images", ctx.params.imageName));
882
+ return { data: [] };
883
+ }
884
+ async listShareLeaf(ctx) {
885
+ if (!(await this.getShares()).find((s) => s.name === ctx.params.shareName)) throw new AFSNotFoundError(joinURL("/shares", ctx.params.shareName));
886
+ return { data: [] };
887
+ }
888
+ async listPackageLeaf(ctx) {
889
+ if (!(await this.getPackages()).find((p) => p.id === ctx.params.packageName)) throw new AFSNotFoundError(joinURL("/packages", ctx.params.packageName));
890
+ return { data: [] };
891
+ }
892
+ async listBackupTaskLeaf(ctx) {
893
+ if (!(await this.getBackupTasks()).find((t) => t.name === ctx.params.taskName)) throw new AFSNotFoundError(joinURL("/backup/tasks", ctx.params.taskName));
894
+ return { data: [] };
895
+ }
896
+ async readRoot(_ctx) {
897
+ const info = await this.getSystemInfo();
898
+ return this.buildEntry("/", {
899
+ content: `model: "${info.model}"\nversion: "${info.version}"\nserial: "${info.serial}"\nuptime_seconds: ${info.uptime}`,
900
+ meta: {
901
+ kind: KINDS.NAS,
902
+ model: info.model,
903
+ version: info.version,
904
+ serial: info.serial,
905
+ childrenCount: 6,
906
+ description: `Synology ${info.model} running DSM ${info.version}`
907
+ }
908
+ });
909
+ }
910
+ async readSystem(_ctx) {
911
+ return this.buildEntry("/system", { meta: {
912
+ kind: KINDS.SYSTEM_GROUP,
913
+ childrenCount: 3,
914
+ description: "System health and resource monitoring"
915
+ } });
916
+ }
917
+ async readSystemStatus(_ctx) {
918
+ const info = await this.getSystemInfo();
919
+ return this.buildEntry("/system/status", {
920
+ content: formatSystemStatus(info),
921
+ meta: {
922
+ kind: KINDS.SYSTEM_INFO,
923
+ childrenCount: 0,
924
+ description: "System health status summary"
925
+ }
926
+ });
927
+ }
928
+ async readSystemUtilization(_ctx) {
929
+ const util = await this.getUtilization();
930
+ return this.buildEntry("/system/utilization", {
931
+ content: formatSystemUtilization(util),
932
+ meta: {
933
+ kind: KINDS.SYSTEM_INFO,
934
+ childrenCount: 0,
935
+ description: "CPU, RAM, disk I/O, network statistics"
936
+ }
937
+ });
938
+ }
939
+ async readSystemHardware(_ctx) {
940
+ const info = await this.getSystemInfo();
941
+ return this.buildEntry("/system/hardware", {
942
+ content: formatHardwareInfo(info),
943
+ meta: {
944
+ kind: KINDS.SYSTEM_INFO,
945
+ childrenCount: 0,
946
+ description: "Temperature, fan speed, power status"
947
+ }
948
+ });
949
+ }
950
+ async readStorage(_ctx) {
951
+ return this.buildEntry("/storage", { meta: {
952
+ kind: KINDS.STORAGE_GROUP,
953
+ childrenCount: 3,
954
+ description: "Physical disks, storage pools, and volumes"
955
+ } });
956
+ }
957
+ async readStorageDisks(_ctx) {
958
+ const disks = await this.getDisks();
959
+ return this.buildEntry("/storage/disks", { meta: {
960
+ childrenCount: disks.length,
961
+ description: "Physical disk drives"
962
+ } });
963
+ }
964
+ async readDisk(ctx) {
965
+ const disk = (await this.getDisks()).find((d) => d.id === ctx.params.diskId);
966
+ if (!disk) throw new AFSNotFoundError(joinURL("/storage/disks", ctx.params.diskId));
967
+ return this.buildEntry(joinURL("/storage/disks", disk.id), {
968
+ content: formatDiskInfo(disk),
969
+ meta: {
970
+ kind: KINDS.DISK,
971
+ id: disk.id,
972
+ childrenCount: 0,
973
+ description: `${disk.model} - ${formatBytes(disk.size_total)} - ${disk.smart_status || disk.status}`
974
+ }
975
+ });
976
+ }
977
+ async readStoragePools(_ctx) {
978
+ const pools = await this.getPools();
979
+ return this.buildEntry("/storage/pools", { meta: {
980
+ childrenCount: pools.length,
981
+ description: "RAID storage pools"
982
+ } });
983
+ }
984
+ async readPool(ctx) {
985
+ const pool = (await this.getPools()).find((p) => p.id === ctx.params.poolId);
986
+ if (!pool) throw new AFSNotFoundError(joinURL("/storage/pools", ctx.params.poolId));
987
+ return this.buildEntry(joinURL("/storage/pools", pool.id), {
988
+ content: formatPoolInfo(pool),
989
+ meta: {
990
+ kind: KINDS.STORAGE_POOL,
991
+ id: pool.id,
992
+ childrenCount: 0,
993
+ description: `${pool.raidType || "unknown"} pool - ${pool.status}`
994
+ }
995
+ });
996
+ }
997
+ async readStorageVolumes(_ctx) {
998
+ const volumes = await this.getVolumes();
999
+ return this.buildEntry("/storage/volumes", { meta: {
1000
+ childrenCount: volumes.length,
1001
+ description: "Logical volumes"
1002
+ } });
1003
+ }
1004
+ async readVolume(ctx) {
1005
+ const volume = (await this.getVolumes()).find((v) => v.id === ctx.params.volumeId);
1006
+ if (!volume) throw new AFSNotFoundError(joinURL("/storage/volumes", ctx.params.volumeId));
1007
+ const usedPercent = Math.round(volume.size.used / volume.size.total * 100);
1008
+ return this.buildEntry(joinURL("/storage/volumes", volume.id), {
1009
+ content: formatVolumeInfo(volume),
1010
+ meta: {
1011
+ kind: KINDS.VOLUME,
1012
+ id: volume.id,
1013
+ childrenCount: 0,
1014
+ description: `${volume.fs_type} - ${usedPercent}% used`
1015
+ }
1016
+ });
1017
+ }
1018
+ async readDocker(_ctx) {
1019
+ return this.buildEntry("/docker", { meta: {
1020
+ kind: KINDS.DOCKER_GROUP,
1021
+ childrenCount: 4,
1022
+ description: "Docker containers, images, networks, and projects"
1023
+ } });
1024
+ }
1025
+ async readDockerContainers(_ctx) {
1026
+ return this.buildEntry("/docker/containers", { meta: {
1027
+ childrenCount: -1,
1028
+ description: "Running and stopped containers"
1029
+ } });
1030
+ }
1031
+ async readContainer(ctx) {
1032
+ const container = (await this.getContainers()).find((c) => c.name === ctx.params.containerName);
1033
+ if (!container) throw new AFSNotFoundError(joinURL("/docker/containers", ctx.params.containerName));
1034
+ return this.buildEntry(joinURL("/docker/containers", container.name), {
1035
+ content: formatContainerInfo(container),
1036
+ meta: {
1037
+ kind: KINDS.CONTAINER,
1038
+ id: container.name,
1039
+ childrenCount: 0,
1040
+ description: `${container.image} - ${container.status}`
1041
+ }
1042
+ });
1043
+ }
1044
+ async readDockerImages(_ctx) {
1045
+ return this.buildEntry("/docker/images", { meta: {
1046
+ childrenCount: -1,
1047
+ description: "Local Docker images"
1048
+ } });
1049
+ }
1050
+ async readImage(ctx) {
1051
+ const image = (await this.getImages()).find((i) => this.sanitizeImageName(i.repository, i.tag) === ctx.params.imageName);
1052
+ if (!image) throw new AFSNotFoundError(joinURL("/docker/images", ctx.params.imageName));
1053
+ const safeName = this.sanitizeImageName(image.repository, image.tag);
1054
+ return this.buildEntry(joinURL("/docker/images", safeName), {
1055
+ content: formatImageInfo(image),
1056
+ meta: {
1057
+ kind: KINDS.DOCKER_IMAGE,
1058
+ id: `${image.repository}:${image.tag}`,
1059
+ childrenCount: 0
1060
+ }
1061
+ });
1062
+ }
1063
+ async readDockerNetworks(_ctx) {
1064
+ return this.buildEntry("/docker/networks", { meta: {
1065
+ childrenCount: 0,
1066
+ description: "Docker networks"
1067
+ } });
1068
+ }
1069
+ async readDockerProjects(_ctx) {
1070
+ return this.buildEntry("/docker/projects", { meta: {
1071
+ childrenCount: 0,
1072
+ description: "Docker Compose projects (DSM 7.2+)"
1073
+ } });
1074
+ }
1075
+ async readSharesDir(_ctx) {
1076
+ const shares = await this.getShares();
1077
+ return this.buildEntry("/shares", { meta: {
1078
+ childrenCount: shares.length,
1079
+ description: "Shared folders"
1080
+ } });
1081
+ }
1082
+ async readShare(ctx) {
1083
+ const share = (await this.getShares()).find((s) => s.name === ctx.params.shareName);
1084
+ if (!share) throw new AFSNotFoundError(joinURL("/shares", ctx.params.shareName));
1085
+ return this.buildEntry(joinURL("/shares", share.name), {
1086
+ content: formatShareInfo(share),
1087
+ meta: {
1088
+ kind: KINDS.SHARED_FOLDER,
1089
+ id: share.name,
1090
+ childrenCount: 0,
1091
+ description: share.path
1092
+ }
1093
+ });
1094
+ }
1095
+ async readPackagesDir(_ctx) {
1096
+ const packages = await this.getPackages();
1097
+ return this.buildEntry("/packages", { meta: {
1098
+ childrenCount: packages.length,
1099
+ description: "Installed DSM packages"
1100
+ } });
1101
+ }
1102
+ async readPackage(ctx) {
1103
+ const pkg = (await this.getPackages()).find((p) => p.id === ctx.params.packageName);
1104
+ if (!pkg) throw new AFSNotFoundError(joinURL("/packages", ctx.params.packageName));
1105
+ const updateStr = pkg.has_update ? " (update available)" : "";
1106
+ return this.buildEntry(joinURL("/packages", pkg.id), {
1107
+ content: formatPackageInfo(pkg),
1108
+ meta: {
1109
+ kind: KINDS.PACKAGE,
1110
+ id: pkg.id,
1111
+ childrenCount: 0,
1112
+ description: `${pkg.name} v${pkg.version} - ${pkg.status}${updateStr}`
1113
+ }
1114
+ });
1115
+ }
1116
+ async readBackupDir(_ctx) {
1117
+ return this.buildEntry("/backup", { meta: {
1118
+ childrenCount: 1,
1119
+ description: "Backup management"
1120
+ } });
1121
+ }
1122
+ async readBackupTasksDir(_ctx) {
1123
+ const tasks = await this.getBackupTasks();
1124
+ return this.buildEntry("/backup/tasks", { meta: {
1125
+ childrenCount: tasks.length,
1126
+ description: "Hyper Backup tasks"
1127
+ } });
1128
+ }
1129
+ async readBackupTask(ctx) {
1130
+ const task = (await this.getBackupTasks()).find((t) => t.name === ctx.params.taskName);
1131
+ if (!task) throw new AFSNotFoundError(joinURL("/backup/tasks", ctx.params.taskName));
1132
+ const lastRunDate = task.last_run ? task.last_run.split("T")[0] : "never";
1133
+ return this.buildEntry(joinURL("/backup/tasks", task.name), {
1134
+ content: formatBackupTaskInfo(task),
1135
+ meta: {
1136
+ kind: KINDS.BACKUP_TASK,
1137
+ id: task.name,
1138
+ childrenCount: 0,
1139
+ description: `${task.status} - last run ${lastRunDate}`
1140
+ }
1141
+ });
1142
+ }
1143
+ async statHandler(ctx) {
1144
+ const entry = (await this.read(ctx.path)).data;
1145
+ if (!entry) throw new AFSNotFoundError(ctx.path);
1146
+ return { data: {
1147
+ id: entry.id,
1148
+ path: entry.path,
1149
+ meta: entry.meta
1150
+ } };
1151
+ }
1152
+ async explainRoot(_ctx) {
1153
+ const info = await this.getSystemInfo();
1154
+ return {
1155
+ format: "markdown",
1156
+ content: `# Synology NAS: ${info.model}
1157
+
1158
+ - **Model**: ${info.model}
1159
+ - **DSM Version**: ${info.version}
1160
+ - **Serial**: ${info.serial}
1161
+ - **Uptime**: ${Math.floor(info.uptime / 3600)}h ${Math.floor(info.uptime % 3600 / 60)}m
1162
+
1163
+ ## Navigation
1164
+
1165
+ - \`/system/\` - System health and resource monitoring
1166
+ - \`/storage/\` - Physical disks, storage pools, and volumes
1167
+ - \`/docker/\` - Docker containers, images, networks
1168
+ - \`/shares/\` - Shared folders
1169
+ - \`/packages/\` - Installed DSM packages
1170
+ - \`/backup/\` - Hyper Backup task management
1171
+ `
1172
+ };
1173
+ }
1174
+ async explainSystem(_ctx) {
1175
+ return {
1176
+ format: "markdown",
1177
+ content: `# System Monitoring
1178
+
1179
+ System health and resource monitoring for the Synology NAS.
1180
+
1181
+ ## Sub-items
1182
+
1183
+ - \`/system/status\` - Overall system health (temperature, uptime)
1184
+ - \`/system/utilization\` - Real-time CPU, RAM, disk I/O, network stats
1185
+ - \`/system/hardware\` - Hardware sensors (temperature, fan speed, power)
1186
+
1187
+ Data sourced from SYNO.Core.System and SYNO.Core.System.Utilization APIs.
1188
+ `
1189
+ };
1190
+ }
1191
+ async explainStorage(_ctx) {
1192
+ return {
1193
+ format: "markdown",
1194
+ content: `# Storage Overview
1195
+
1196
+ Hierarchical view of physical storage:
1197
+
1198
+ \`\`\`
1199
+ Physical Disks --> Storage Pools (RAID) --> Volumes (filesystem)
1200
+ \`\`\`
1201
+
1202
+ ## Sub-items
1203
+
1204
+ - \`/storage/disks/\` - Physical disk drives with SMART status
1205
+ - \`/storage/pools/\` - RAID storage pools
1206
+ - \`/storage/volumes/\` - Logical volumes with usage info
1207
+
1208
+ Data sourced from SYNO.Storage.CGI.Storage API.
1209
+ `
1210
+ };
1211
+ }
1212
+ async explainDocker(_ctx) {
1213
+ const containers = await this.getContainers();
1214
+ const running = containers.filter((c) => c.status === "running").length;
1215
+ return {
1216
+ format: "markdown",
1217
+ content: `# Docker Environment
1218
+
1219
+ Container management for the Synology NAS.
1220
+
1221
+ - **Running containers**: ${running}
1222
+ - **Stopped containers**: ${containers.length - running}
1223
+
1224
+ ## Sub-items
1225
+
1226
+ - \`/docker/containers/\` - Container list and details
1227
+ - \`/docker/images/\` - Local Docker images
1228
+ - \`/docker/networks/\` - Docker networks
1229
+ - \`/docker/projects/\` - Docker Compose projects (DSM 7.2+)
1230
+
1231
+ ## Actions
1232
+
1233
+ Containers support the following actions via exec:
1234
+ - \`start\` - Start a stopped container
1235
+ - \`stop\` - Stop a running container
1236
+ - \`restart\` - Restart a container
1237
+ `
1238
+ };
1239
+ }
1240
+ async explainGeneric(ctx) {
1241
+ const entry = (await this.read(ctx.path)).data;
1242
+ if (!entry) throw new AFSNotFoundError(ctx.path);
1243
+ const name = ctx.path.split("/").filter(Boolean).pop() || ctx.path;
1244
+ const desc = entry.meta?.description || "No description available";
1245
+ const kind = entry.meta?.kind || "unknown";
1246
+ let content = `# ${name}\n\n- **Path**: \`${ctx.path}\`\n- **Kind**: ${kind}\n- **Description**: ${desc}\n`;
1247
+ if (entry.content && typeof entry.content === "string") content += `\n## Data\n\n\`\`\`yaml\n${entry.content}\n\`\`\`\n`;
1248
+ return {
1249
+ format: "markdown",
1250
+ content
1251
+ };
1252
+ }
1253
+ async searchHandler(ctx, query, options) {
1254
+ const searchPath = ctx.params.path ? `/${ctx.params.path}` : "/";
1255
+ const limit = options?.limit ?? 100;
1256
+ const searchQuery = query.toLowerCase();
1257
+ const entries = [];
1258
+ const collectEntries = async (listPath) => {
1259
+ try {
1260
+ const result = await this.list(listPath, { maxDepth: 10 });
1261
+ for (const entry of result.data) entries.push(entry);
1262
+ } catch {}
1263
+ };
1264
+ await collectEntries(searchPath);
1265
+ return { data: entries.filter((entry) => {
1266
+ if (!searchQuery) return true;
1267
+ return [
1268
+ entry.path.split("/").filter(Boolean).pop() || "",
1269
+ String(entry.meta?.id || entry.id || ""),
1270
+ String(entry.meta?.description || ""),
1271
+ String(entry.meta?.kind || "")
1272
+ ].join(" ").toLowerCase().includes(searchQuery);
1273
+ }).slice(0, limit) };
1274
+ }
1275
+ async listContainerActions(ctx) {
1276
+ if (!(await this.getContainers()).find((c) => c.name === ctx.params.containerName)) throw new AFSNotFoundError(joinURL("/docker/containers", ctx.params.containerName));
1277
+ return { data: [
1278
+ {
1279
+ id: "start",
1280
+ path: joinURL(ctx.path, "start"),
1281
+ summary: "Start the container",
1282
+ meta: {
1283
+ kind: "afs:executable",
1284
+ kinds: ["afs:executable", "afs:node"],
1285
+ severity: "boundary",
1286
+ inputSchema: {
1287
+ type: "object",
1288
+ properties: {}
1289
+ }
1290
+ }
1291
+ },
1292
+ {
1293
+ id: "stop",
1294
+ path: joinURL(ctx.path, "stop"),
1295
+ summary: "Stop the container",
1296
+ meta: {
1297
+ kind: "afs:executable",
1298
+ kinds: ["afs:executable", "afs:node"],
1299
+ severity: "boundary",
1300
+ inputSchema: {
1301
+ type: "object",
1302
+ properties: {}
1303
+ }
1304
+ }
1305
+ },
1306
+ {
1307
+ id: "restart",
1308
+ path: joinURL(ctx.path, "restart"),
1309
+ summary: "Restart the container",
1310
+ meta: {
1311
+ kind: "afs:executable",
1312
+ kinds: ["afs:executable", "afs:node"],
1313
+ severity: "boundary",
1314
+ inputSchema: {
1315
+ type: "object",
1316
+ properties: {}
1317
+ }
1318
+ }
1319
+ }
1320
+ ] };
1321
+ }
1322
+ async startContainerAction(ctx, _args) {
1323
+ if (!(await this.getContainers()).find((c) => c.name === ctx.params.containerName)) throw new AFSNotFoundError(joinURL("/docker/containers", ctx.params.containerName));
1324
+ try {
1325
+ await this.client.request({
1326
+ api: "SYNO.Docker.Container",
1327
+ method: "start",
1328
+ version: 1,
1329
+ extra: { name: ctx.params.containerName }
1330
+ });
1331
+ } catch (error) {
1332
+ if (error instanceof AFSError) return {
1333
+ success: false,
1334
+ error: {
1335
+ code: "CONTAINER_ERROR",
1336
+ message: error.message
1337
+ }
1338
+ };
1339
+ throw error;
1340
+ }
1341
+ this.invalidateContainerCache();
1342
+ return {
1343
+ success: true,
1344
+ data: {
1345
+ container: ctx.params.containerName,
1346
+ action: "start",
1347
+ status: "running"
1348
+ }
1349
+ };
1350
+ }
1351
+ async stopContainerAction(ctx, _args) {
1352
+ if (!(await this.getContainers()).find((c) => c.name === ctx.params.containerName)) throw new AFSNotFoundError(joinURL("/docker/containers", ctx.params.containerName));
1353
+ try {
1354
+ await this.client.request({
1355
+ api: "SYNO.Docker.Container",
1356
+ method: "stop",
1357
+ version: 1,
1358
+ extra: { name: ctx.params.containerName }
1359
+ });
1360
+ } catch (error) {
1361
+ if (error instanceof AFSError) return {
1362
+ success: false,
1363
+ error: {
1364
+ code: "CONTAINER_ERROR",
1365
+ message: error.message
1366
+ }
1367
+ };
1368
+ throw error;
1369
+ }
1370
+ this.invalidateContainerCache();
1371
+ return {
1372
+ success: true,
1373
+ data: {
1374
+ container: ctx.params.containerName,
1375
+ action: "stop",
1376
+ status: "stopped"
1377
+ }
1378
+ };
1379
+ }
1380
+ async restartContainerAction(ctx, _args) {
1381
+ if (!(await this.getContainers()).find((c) => c.name === ctx.params.containerName)) throw new AFSNotFoundError(joinURL("/docker/containers", ctx.params.containerName));
1382
+ try {
1383
+ await this.client.request({
1384
+ api: "SYNO.Docker.Container",
1385
+ method: "restart",
1386
+ version: 1,
1387
+ extra: { name: ctx.params.containerName }
1388
+ });
1389
+ } catch (error) {
1390
+ if (error instanceof AFSError) return {
1391
+ success: false,
1392
+ error: {
1393
+ code: "CONTAINER_ERROR",
1394
+ message: error.message
1395
+ }
1396
+ };
1397
+ throw error;
1398
+ }
1399
+ this.invalidateContainerCache();
1400
+ return {
1401
+ success: true,
1402
+ data: {
1403
+ container: ctx.params.containerName,
1404
+ action: "restart",
1405
+ status: "running"
1406
+ }
1407
+ };
1408
+ }
1409
+ async listBackupTaskActions(ctx) {
1410
+ if (!(await this.getBackupTasks()).find((t) => t.name === ctx.params.taskName)) throw new AFSNotFoundError(joinURL("/backup/tasks", ctx.params.taskName));
1411
+ return { data: [{
1412
+ id: "run",
1413
+ path: joinURL(ctx.path, "run"),
1414
+ summary: "Trigger backup execution",
1415
+ meta: {
1416
+ kind: "afs:executable",
1417
+ kinds: ["afs:executable", "afs:node"],
1418
+ severity: "boundary",
1419
+ inputSchema: {
1420
+ type: "object",
1421
+ properties: {}
1422
+ }
1423
+ }
1424
+ }] };
1425
+ }
1426
+ async runBackupTaskAction(ctx, _args) {
1427
+ if (!(await this.getBackupTasks()).find((t) => t.name === ctx.params.taskName)) throw new AFSNotFoundError(joinURL("/backup/tasks", ctx.params.taskName));
1428
+ try {
1429
+ await this.client.request({
1430
+ api: "SYNO.Backup.Task",
1431
+ method: "run",
1432
+ version: 1,
1433
+ extra: { name: ctx.params.taskName }
1434
+ });
1435
+ } catch (error) {
1436
+ if (error instanceof AFSError) return {
1437
+ success: false,
1438
+ error: {
1439
+ code: "BACKUP_ERROR",
1440
+ message: error.message
1441
+ }
1442
+ };
1443
+ throw error;
1444
+ }
1445
+ this.invalidateBackupTaskCache();
1446
+ return {
1447
+ success: true,
1448
+ data: {
1449
+ task: ctx.params.taskName,
1450
+ action: "run",
1451
+ status: "running"
1452
+ }
1453
+ };
1454
+ }
1455
+ async listPackageActions(ctx) {
1456
+ if (!(await this.getPackages()).find((p) => p.id === ctx.params.packageName)) throw new AFSNotFoundError(joinURL("/packages", ctx.params.packageName));
1457
+ return { data: [{
1458
+ id: "start",
1459
+ path: joinURL(ctx.path, "start"),
1460
+ summary: "Start the package",
1461
+ meta: {
1462
+ kind: "afs:executable",
1463
+ kinds: ["afs:executable", "afs:node"],
1464
+ severity: "boundary",
1465
+ inputSchema: {
1466
+ type: "object",
1467
+ properties: {}
1468
+ }
1469
+ }
1470
+ }, {
1471
+ id: "stop",
1472
+ path: joinURL(ctx.path, "stop"),
1473
+ summary: "Stop the package",
1474
+ meta: {
1475
+ kind: "afs:executable",
1476
+ kinds: ["afs:executable", "afs:node"],
1477
+ severity: "boundary",
1478
+ inputSchema: {
1479
+ type: "object",
1480
+ properties: {}
1481
+ }
1482
+ }
1483
+ }] };
1484
+ }
1485
+ async startPackageAction(ctx, _args) {
1486
+ if (!(await this.getPackages()).find((p) => p.id === ctx.params.packageName)) throw new AFSNotFoundError(joinURL("/packages", ctx.params.packageName));
1487
+ try {
1488
+ await this.client.request({
1489
+ api: "SYNO.Core.Package",
1490
+ method: "start",
1491
+ version: 1,
1492
+ extra: { id: ctx.params.packageName }
1493
+ });
1494
+ } catch (error) {
1495
+ if (error instanceof AFSError) return {
1496
+ success: false,
1497
+ error: {
1498
+ code: "PACKAGE_ERROR",
1499
+ message: error.message
1500
+ }
1501
+ };
1502
+ throw error;
1503
+ }
1504
+ this.invalidatePackageCache();
1505
+ return {
1506
+ success: true,
1507
+ data: {
1508
+ package: ctx.params.packageName,
1509
+ action: "start",
1510
+ status: "running"
1511
+ }
1512
+ };
1513
+ }
1514
+ async stopPackageAction(ctx, _args) {
1515
+ if (!(await this.getPackages()).find((p) => p.id === ctx.params.packageName)) throw new AFSNotFoundError(joinURL("/packages", ctx.params.packageName));
1516
+ try {
1517
+ await this.client.request({
1518
+ api: "SYNO.Core.Package",
1519
+ method: "stop",
1520
+ version: 1,
1521
+ extra: { id: ctx.params.packageName }
1522
+ });
1523
+ } catch (error) {
1524
+ if (error instanceof AFSError) return {
1525
+ success: false,
1526
+ error: {
1527
+ code: "PACKAGE_ERROR",
1528
+ message: error.message
1529
+ }
1530
+ };
1531
+ throw error;
1532
+ }
1533
+ this.invalidatePackageCache();
1534
+ return {
1535
+ success: true,
1536
+ data: {
1537
+ package: ctx.params.packageName,
1538
+ action: "stop",
1539
+ status: "stopped"
1540
+ }
1541
+ };
1542
+ }
1543
+ async destroy() {
1544
+ await this.client.destroy();
1545
+ }
1546
+ };
1547
+ __decorate([Read("/.meta/.capabilities")], AFSSynology.prototype, "readCapabilities", null);
1548
+ __decorate([Meta("/")], AFSSynology.prototype, "metaRoot", null);
1549
+ __decorate([Meta("/system")], AFSSynology.prototype, "metaSystem", null);
1550
+ __decorate([Meta("/system/:item")], AFSSynology.prototype, "metaSystemItem", null);
1551
+ __decorate([Meta("/storage")], AFSSynology.prototype, "metaStorage", null);
1552
+ __decorate([Meta("/storage/disks")], AFSSynology.prototype, "metaDisks", null);
1553
+ __decorate([Meta("/storage/disks/:diskId")], AFSSynology.prototype, "metaDisk", null);
1554
+ __decorate([Meta("/storage/pools")], AFSSynology.prototype, "metaPools", null);
1555
+ __decorate([Meta("/storage/pools/:poolId")], AFSSynology.prototype, "metaPool", null);
1556
+ __decorate([Meta("/storage/volumes")], AFSSynology.prototype, "metaVolumes", null);
1557
+ __decorate([Meta("/storage/volumes/:volumeId")], AFSSynology.prototype, "metaVolume", null);
1558
+ __decorate([Meta("/docker")], AFSSynology.prototype, "metaDocker", null);
1559
+ __decorate([Meta("/docker/containers")], AFSSynology.prototype, "metaContainers", null);
1560
+ __decorate([Meta("/docker/containers/:containerName")], AFSSynology.prototype, "metaContainer", null);
1561
+ __decorate([Meta("/docker/images")], AFSSynology.prototype, "metaImages", null);
1562
+ __decorate([Meta("/docker/images/:imageName")], AFSSynology.prototype, "metaImage", null);
1563
+ __decorate([Meta("/docker/networks")], AFSSynology.prototype, "metaNetworks", null);
1564
+ __decorate([Meta("/docker/projects")], AFSSynology.prototype, "metaProjects", null);
1565
+ __decorate([Meta("/shares")], AFSSynology.prototype, "metaShares", null);
1566
+ __decorate([Meta("/shares/:shareName")], AFSSynology.prototype, "metaShare", null);
1567
+ __decorate([Meta("/packages")], AFSSynology.prototype, "metaPackages", null);
1568
+ __decorate([Meta("/packages/:packageName")], AFSSynology.prototype, "metaPackage", null);
1569
+ __decorate([Meta("/backup")], AFSSynology.prototype, "metaBackup", null);
1570
+ __decorate([Meta("/backup/tasks")], AFSSynology.prototype, "metaBackupTasks", null);
1571
+ __decorate([Meta("/backup/tasks/:taskName")], AFSSynology.prototype, "metaBackupTask", null);
1572
+ __decorate([List("/")], AFSSynology.prototype, "listRoot", null);
1573
+ __decorate([List("/system")], AFSSynology.prototype, "listSystem", null);
1574
+ __decorate([List("/storage")], AFSSynology.prototype, "listStorage", null);
1575
+ __decorate([List("/storage/disks")], AFSSynology.prototype, "listDisks", null);
1576
+ __decorate([List("/storage/pools")], AFSSynology.prototype, "listPools", null);
1577
+ __decorate([List("/storage/volumes")], AFSSynology.prototype, "listVolumes", null);
1578
+ __decorate([List("/docker")], AFSSynology.prototype, "listDocker", null);
1579
+ __decorate([List("/docker/containers")], AFSSynology.prototype, "listContainers", null);
1580
+ __decorate([List("/docker/images")], AFSSynology.prototype, "listImages", null);
1581
+ __decorate([List("/docker/networks")], AFSSynology.prototype, "listNetworks", null);
1582
+ __decorate([List("/docker/projects")], AFSSynology.prototype, "listProjects", null);
1583
+ __decorate([List("/shares")], AFSSynology.prototype, "listShares", null);
1584
+ __decorate([List("/packages")], AFSSynology.prototype, "listPackages", null);
1585
+ __decorate([List("/backup")], AFSSynology.prototype, "listBackup", null);
1586
+ __decorate([List("/backup/tasks")], AFSSynology.prototype, "listBackupTasks", null);
1587
+ __decorate([List("/system/:item")], AFSSynology.prototype, "listSystemLeaf", null);
1588
+ __decorate([List("/storage/disks/:diskId")], AFSSynology.prototype, "listDiskLeaf", null);
1589
+ __decorate([List("/storage/pools/:poolId")], AFSSynology.prototype, "listPoolLeaf", null);
1590
+ __decorate([List("/storage/volumes/:volumeId")], AFSSynology.prototype, "listVolumeLeaf", null);
1591
+ __decorate([List("/docker/containers/:containerName")], AFSSynology.prototype, "listContainerLeaf", null);
1592
+ __decorate([List("/docker/images/:imageName")], AFSSynology.prototype, "listImageLeaf", null);
1593
+ __decorate([List("/shares/:shareName")], AFSSynology.prototype, "listShareLeaf", null);
1594
+ __decorate([List("/packages/:packageName")], AFSSynology.prototype, "listPackageLeaf", null);
1595
+ __decorate([List("/backup/tasks/:taskName")], AFSSynology.prototype, "listBackupTaskLeaf", null);
1596
+ __decorate([Read("/")], AFSSynology.prototype, "readRoot", null);
1597
+ __decorate([Read("/system")], AFSSynology.prototype, "readSystem", null);
1598
+ __decorate([Read("/system/status")], AFSSynology.prototype, "readSystemStatus", null);
1599
+ __decorate([Read("/system/utilization")], AFSSynology.prototype, "readSystemUtilization", null);
1600
+ __decorate([Read("/system/hardware")], AFSSynology.prototype, "readSystemHardware", null);
1601
+ __decorate([Read("/storage")], AFSSynology.prototype, "readStorage", null);
1602
+ __decorate([Read("/storage/disks")], AFSSynology.prototype, "readStorageDisks", null);
1603
+ __decorate([Read("/storage/disks/:diskId")], AFSSynology.prototype, "readDisk", null);
1604
+ __decorate([Read("/storage/pools")], AFSSynology.prototype, "readStoragePools", null);
1605
+ __decorate([Read("/storage/pools/:poolId")], AFSSynology.prototype, "readPool", null);
1606
+ __decorate([Read("/storage/volumes")], AFSSynology.prototype, "readStorageVolumes", null);
1607
+ __decorate([Read("/storage/volumes/:volumeId")], AFSSynology.prototype, "readVolume", null);
1608
+ __decorate([Read("/docker")], AFSSynology.prototype, "readDocker", null);
1609
+ __decorate([Read("/docker/containers")], AFSSynology.prototype, "readDockerContainers", null);
1610
+ __decorate([Read("/docker/containers/:containerName")], AFSSynology.prototype, "readContainer", null);
1611
+ __decorate([Read("/docker/images")], AFSSynology.prototype, "readDockerImages", null);
1612
+ __decorate([Read("/docker/images/:imageName")], AFSSynology.prototype, "readImage", null);
1613
+ __decorate([Read("/docker/networks")], AFSSynology.prototype, "readDockerNetworks", null);
1614
+ __decorate([Read("/docker/projects")], AFSSynology.prototype, "readDockerProjects", null);
1615
+ __decorate([Read("/shares")], AFSSynology.prototype, "readSharesDir", null);
1616
+ __decorate([Read("/shares/:shareName")], AFSSynology.prototype, "readShare", null);
1617
+ __decorate([Read("/packages")], AFSSynology.prototype, "readPackagesDir", null);
1618
+ __decorate([Read("/packages/:packageName")], AFSSynology.prototype, "readPackage", null);
1619
+ __decorate([Read("/backup")], AFSSynology.prototype, "readBackupDir", null);
1620
+ __decorate([Read("/backup/tasks")], AFSSynology.prototype, "readBackupTasksDir", null);
1621
+ __decorate([Read("/backup/tasks/:taskName")], AFSSynology.prototype, "readBackupTask", null);
1622
+ __decorate([
1623
+ Stat("/"),
1624
+ Stat("/system"),
1625
+ Stat("/system/:item"),
1626
+ Stat("/storage"),
1627
+ Stat("/storage/disks"),
1628
+ Stat("/storage/disks/:diskId"),
1629
+ Stat("/storage/pools"),
1630
+ Stat("/storage/pools/:poolId"),
1631
+ Stat("/storage/volumes"),
1632
+ Stat("/storage/volumes/:volumeId"),
1633
+ Stat("/docker"),
1634
+ Stat("/docker/containers"),
1635
+ Stat("/docker/containers/:containerName"),
1636
+ Stat("/docker/images"),
1637
+ Stat("/docker/images/:imageName"),
1638
+ Stat("/docker/networks"),
1639
+ Stat("/docker/projects"),
1640
+ Stat("/shares"),
1641
+ Stat("/shares/:shareName"),
1642
+ Stat("/packages"),
1643
+ Stat("/packages/:packageName"),
1644
+ Stat("/backup"),
1645
+ Stat("/backup/tasks"),
1646
+ Stat("/backup/tasks/:taskName")
1647
+ ], AFSSynology.prototype, "statHandler", null);
1648
+ __decorate([Explain("/")], AFSSynology.prototype, "explainRoot", null);
1649
+ __decorate([Explain("/system")], AFSSynology.prototype, "explainSystem", null);
1650
+ __decorate([Explain("/storage")], AFSSynology.prototype, "explainStorage", null);
1651
+ __decorate([Explain("/docker")], AFSSynology.prototype, "explainDocker", null);
1652
+ __decorate([
1653
+ Explain("/system/:item"),
1654
+ Explain("/storage/disks"),
1655
+ Explain("/storage/disks/:diskId"),
1656
+ Explain("/storage/pools"),
1657
+ Explain("/storage/pools/:poolId"),
1658
+ Explain("/storage/volumes"),
1659
+ Explain("/storage/volumes/:volumeId"),
1660
+ Explain("/docker/containers"),
1661
+ Explain("/docker/containers/:containerName"),
1662
+ Explain("/docker/images"),
1663
+ Explain("/docker/images/:imageName"),
1664
+ Explain("/docker/networks"),
1665
+ Explain("/docker/projects"),
1666
+ Explain("/shares"),
1667
+ Explain("/shares/:shareName"),
1668
+ Explain("/packages"),
1669
+ Explain("/packages/:packageName"),
1670
+ Explain("/backup"),
1671
+ Explain("/backup/tasks"),
1672
+ Explain("/backup/tasks/:taskName")
1673
+ ], AFSSynology.prototype, "explainGeneric", null);
1674
+ __decorate([Search("/"), Search("/:path*")], AFSSynology.prototype, "searchHandler", null);
1675
+ __decorate([Actions("/docker/containers/:containerName")], AFSSynology.prototype, "listContainerActions", null);
1676
+ __decorate([Actions.Exec("/docker/containers/:containerName", "start")], AFSSynology.prototype, "startContainerAction", null);
1677
+ __decorate([Actions.Exec("/docker/containers/:containerName", "stop")], AFSSynology.prototype, "stopContainerAction", null);
1678
+ __decorate([Actions.Exec("/docker/containers/:containerName", "restart")], AFSSynology.prototype, "restartContainerAction", null);
1679
+ __decorate([Actions("/backup/tasks/:taskName")], AFSSynology.prototype, "listBackupTaskActions", null);
1680
+ __decorate([Actions.Exec("/backup/tasks/:taskName", "run")], AFSSynology.prototype, "runBackupTaskAction", null);
1681
+ __decorate([Actions("/packages/:packageName")], AFSSynology.prototype, "listPackageActions", null);
1682
+ __decorate([Actions.Exec("/packages/:packageName", "start")], AFSSynology.prototype, "startPackageAction", null);
1683
+ __decorate([Actions.Exec("/packages/:packageName", "stop")], AFSSynology.prototype, "stopPackageAction", null);
1684
+
1685
+ //#endregion
1686
+ export { AFSSynology };
1687
+ //# sourceMappingURL=synology-afs.mjs.map