@aigne/afs-frigate 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.
package/dist/index.cjs ADDED
@@ -0,0 +1,2333 @@
1
+ Object.defineProperty(exports, '__esModule', { value: true });
2
+ const require_decorate = require('./_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.cjs');
3
+ let _aigne_afs = require("@aigne/afs");
4
+ let _aigne_afs_provider = require("@aigne/afs/provider");
5
+ let ufo = require("ufo");
6
+ let zod = require("zod");
7
+
8
+ //#region src/index.ts
9
+ /**
10
+ * AFSFrigate - AFS Provider for Frigate NVR
11
+ *
12
+ * Maps Frigate NVR into a virtual filesystem.
13
+ * Phases:
14
+ * - 0a: Core skeleton + conformance (cameras, events, dashboard, config)
15
+ * - 0b (Phase 1): Full readonly (binary content, recordings, reviews, search, summaries)
16
+ * - 0c (Phase 2): MQTT cache optimization (optional, graceful fallback)
17
+ * - 1 (Phase 3): Event management + camera control (readwrite actions)
18
+ * - 2 (Phase 4): Exports + semantic search + AI + PTZ
19
+ * - 3 (Phase 5): Configuration management + logs + users + restart
20
+ */
21
+ const afsFrigateOptionsSchema = zod.z.object({
22
+ name: zod.z.string().default("frigate"),
23
+ description: zod.z.string().optional(),
24
+ url: zod.z.string(),
25
+ username: zod.z.string().optional(),
26
+ password: zod.z.string().optional(),
27
+ mqttUrl: zod.z.string().optional(),
28
+ mqttTopicPrefix: zod.z.string().default("frigate"),
29
+ cacheTtl: zod.z.number().default(30),
30
+ accessMode: zod.z.enum(["readonly", "readwrite"]).default("readwrite")
31
+ });
32
+ var FrigateHttpClient = class {
33
+ baseUrl;
34
+ headers;
35
+ cache = /* @__PURE__ */ new Map();
36
+ cacheTtl;
37
+ pendingRequests = /* @__PURE__ */ new Map();
38
+ constructor(options) {
39
+ const urlLower = options.url.toLowerCase();
40
+ if (!urlLower.startsWith("http://") && !urlLower.startsWith("https://")) throw new Error("Frigate URL must use http:// or https:// protocol");
41
+ if (urlLower.startsWith("javascript:") || urlLower.startsWith("file:") || urlLower.startsWith("data:")) throw new Error("Invalid URL protocol");
42
+ this.baseUrl = options.url.replace(/\/$/, "");
43
+ this.cacheTtl = (options.cacheTtl ?? 30) * 1e3;
44
+ this.headers = {};
45
+ if (options.username && options.password) {
46
+ const credentials = btoa(`${options.username}:${options.password}`);
47
+ this.headers.Authorization = `Basic ${credentials}`;
48
+ }
49
+ }
50
+ getBaseUrl() {
51
+ return this.baseUrl;
52
+ }
53
+ invalidateCache(prefix) {
54
+ if (!prefix) {
55
+ this.cache.clear();
56
+ return;
57
+ }
58
+ for (const key of this.cache.keys()) if (key.includes(prefix)) this.cache.delete(key);
59
+ }
60
+ async fetchJSON(path, params) {
61
+ const url = new URL((0, ufo.joinURL)(this.baseUrl, path));
62
+ if (params) for (const [key, value] of Object.entries(params)) url.searchParams.set(key, value);
63
+ const cacheKey = url.toString();
64
+ const cached = this.cache.get(cacheKey);
65
+ if (cached && cached.expiresAt > Date.now()) return cached.data;
66
+ const pending = this.pendingRequests.get(cacheKey);
67
+ if (pending) return pending;
68
+ const requestPromise = (async () => {
69
+ try {
70
+ const response = await fetch(url.toString(), {
71
+ headers: this.headers,
72
+ signal: AbortSignal.timeout(1e4)
73
+ });
74
+ if (response.status === 401 || response.status === 403) throw new FrigateApiError("Authentication failed", "AFS_AUTH_FAILED", response.status);
75
+ if (response.status === 404) throw new FrigateApiError("Not found", "AFS_NOT_FOUND", 404);
76
+ if (response.status >= 500) throw new FrigateApiError("Upstream server error", "AFS_UPSTREAM_ERROR", response.status);
77
+ if (!response.ok) throw new FrigateApiError("Unexpected response", "AFS_UPSTREAM_ERROR", response.status);
78
+ const data = await response.json();
79
+ this.cache.set(cacheKey, {
80
+ data,
81
+ expiresAt: Date.now() + this.cacheTtl
82
+ });
83
+ return data;
84
+ } catch (error) {
85
+ if (error instanceof FrigateApiError) throw error;
86
+ if (error instanceof TypeError || error?.code === "ECONNREFUSED") throw new FrigateApiError("Connection failed", "AFS_CONNECTION_ERROR", 0);
87
+ if (error?.name === "TimeoutError" || error?.name === "AbortError") throw new FrigateApiError("Connection timed out", "AFS_CONNECTION_ERROR", 0);
88
+ throw error;
89
+ } finally {
90
+ this.pendingRequests.delete(cacheKey);
91
+ }
92
+ })();
93
+ this.pendingRequests.set(cacheKey, requestPromise);
94
+ return requestPromise;
95
+ }
96
+ async fetchRaw(path, options) {
97
+ const url = (0, ufo.joinURL)(this.baseUrl, path);
98
+ const method = options?.method ?? "GET";
99
+ const fetchHeaders = { ...this.headers };
100
+ let body;
101
+ if (options?.body) if (typeof options.body === "string") {
102
+ body = options.body;
103
+ fetchHeaders["Content-Type"] = "text/plain";
104
+ } else {
105
+ body = JSON.stringify(options.body);
106
+ fetchHeaders["Content-Type"] = "application/json";
107
+ }
108
+ try {
109
+ const response = await fetch(url, {
110
+ method,
111
+ headers: fetchHeaders,
112
+ body,
113
+ signal: AbortSignal.timeout(1e4)
114
+ });
115
+ if (response.status === 401 || response.status === 403) throw new FrigateApiError("Authentication failed", "AFS_AUTH_FAILED", response.status);
116
+ if (response.status === 404) throw new FrigateApiError("Not found", "AFS_NOT_FOUND", 404);
117
+ if (response.status >= 500) throw new FrigateApiError("Upstream server error", "AFS_UPSTREAM_ERROR", response.status);
118
+ if (!response.ok) throw new FrigateApiError("Unexpected response", "AFS_UPSTREAM_ERROR", response.status);
119
+ return response;
120
+ } catch (error) {
121
+ if (error instanceof FrigateApiError) throw error;
122
+ if (error instanceof TypeError || error?.code === "ECONNREFUSED") throw new FrigateApiError("Connection failed", "AFS_CONNECTION_ERROR", 0);
123
+ if (error?.name === "TimeoutError" || error?.name === "AbortError") throw new FrigateApiError("Connection timed out", "AFS_CONNECTION_ERROR", 0);
124
+ throw error;
125
+ }
126
+ }
127
+ async fetchBinary(path) {
128
+ return (await this.fetchRaw(path)).arrayBuffer();
129
+ }
130
+ async fetchText(path) {
131
+ return (await this.fetchRaw(path)).text();
132
+ }
133
+ async postJSON(path, body) {
134
+ return await (await this.fetchRaw(path, {
135
+ method: "POST",
136
+ body
137
+ })).json();
138
+ }
139
+ async postText(path, body) {
140
+ return (await this.fetchRaw(path, {
141
+ method: "POST",
142
+ body
143
+ })).json();
144
+ }
145
+ async putJSON(path, body) {
146
+ return await (await this.fetchRaw(path, {
147
+ method: "PUT",
148
+ body
149
+ })).json();
150
+ }
151
+ async patchJSON(path, body) {
152
+ return await (await this.fetchRaw(path, {
153
+ method: "PATCH",
154
+ body
155
+ })).json();
156
+ }
157
+ async deleteRequest(path) {
158
+ return await (await this.fetchRaw(path, { method: "DELETE" })).json();
159
+ }
160
+ async getConfig() {
161
+ return this.fetchJSON("/api/config");
162
+ }
163
+ async getStats() {
164
+ return this.fetchJSON("/api/stats");
165
+ }
166
+ async getEvents(params) {
167
+ return this.fetchJSON("/api/events", params);
168
+ }
169
+ async getEvent(id) {
170
+ return this.fetchJSON(`/api/events/${id}`);
171
+ }
172
+ async getEventsSummary() {
173
+ return this.fetchJSON("/api/events/summary");
174
+ }
175
+ async searchEvents(params) {
176
+ return this.fetchJSON("/api/events/search", params);
177
+ }
178
+ async getReviews(params) {
179
+ return this.fetchJSON("/api/review", params);
180
+ }
181
+ async getReview(id) {
182
+ return this.fetchJSON(`/api/review/${id}`);
183
+ }
184
+ async getReviewSummary() {
185
+ return this.fetchJSON("/api/review/summary");
186
+ }
187
+ async getRecordings(camera) {
188
+ return this.fetchJSON(`/api/${camera}/recordings`);
189
+ }
190
+ async getRecordingsSummary(camera) {
191
+ return this.fetchJSON(`/api/${camera}/recordings/summary`);
192
+ }
193
+ async getRecordingsStorage() {
194
+ return this.fetchJSON("/api/recordings/storage");
195
+ }
196
+ async getExports() {
197
+ return this.fetchJSON("/api/exports");
198
+ }
199
+ async getExport(id) {
200
+ return this.fetchJSON(`/api/exports/${id}`);
201
+ }
202
+ async getUsers() {
203
+ return this.fetchJSON("/api/users");
204
+ }
205
+ async getPtzInfo(camera) {
206
+ return this.fetchJSON(`/api/${camera}/ptz/info`);
207
+ }
208
+ async getReviewActivitySummary() {
209
+ return this.fetchJSON("/api/review/activity/summary");
210
+ }
211
+ };
212
+ var FrigateApiError = class extends Error {
213
+ code;
214
+ statusCode;
215
+ constructor(message, code, statusCode) {
216
+ super(message);
217
+ this.name = "FrigateApiError";
218
+ this.code = code;
219
+ this.statusCode = statusCode;
220
+ }
221
+ };
222
+ function isPathTraversal(segment) {
223
+ return segment.includes("..") || segment.includes("./") || segment.startsWith("/");
224
+ }
225
+ function createMqttManager(_mqttUrl, _topicPrefix, _onInvalidate) {
226
+ try {
227
+ return null;
228
+ } catch {
229
+ return null;
230
+ }
231
+ }
232
+ var AFSFrigate = class AFSFrigate extends _aigne_afs_provider.AFSBaseProvider {
233
+ static securityProfiles() {
234
+ return {
235
+ admin: {
236
+ actionPolicy: "full",
237
+ sensitivity: "full"
238
+ },
239
+ user: {
240
+ actionPolicy: "standard",
241
+ blockedActions: ["restart", "delete"],
242
+ sensitivity: "redacted"
243
+ },
244
+ guest: {
245
+ actionPolicy: "safe",
246
+ accessMode: "readonly",
247
+ sensitivity: "redacted"
248
+ }
249
+ };
250
+ }
251
+ static schema() {
252
+ return afsFrigateOptionsSchema;
253
+ }
254
+ static manifest() {
255
+ return {
256
+ name: "frigate",
257
+ description: "Frigate NVR — cameras, events, and recordings as filesystem",
258
+ uriTemplate: "frigate://{host}",
259
+ category: "iot",
260
+ schema: zod.z.object({ host: zod.z.string() }),
261
+ tags: [
262
+ "frigate",
263
+ "nvr",
264
+ "camera",
265
+ "security",
266
+ "surveillance"
267
+ ],
268
+ capabilityTags: [
269
+ "read-only",
270
+ "search",
271
+ "auth:token",
272
+ "remote",
273
+ "http",
274
+ "on-premise",
275
+ "real-time"
276
+ ],
277
+ security: {
278
+ riskLevel: "external",
279
+ resourceAccess: ["internet", "local-network"],
280
+ dataSensitivity: ["media", "personal-data"],
281
+ notes: ["Accesses surveillance cameras and recordings — contains video of people and property"]
282
+ },
283
+ capabilities: { network: { egress: true } }
284
+ };
285
+ }
286
+ static treeSchema() {
287
+ return {
288
+ operations: [
289
+ "list",
290
+ "read",
291
+ "write",
292
+ "search",
293
+ "exec",
294
+ "stat",
295
+ "explain"
296
+ ],
297
+ tree: {
298
+ "/": {
299
+ kind: "frigate:nvr",
300
+ actions: ["restart"],
301
+ destructive: ["restart"]
302
+ },
303
+ "/cameras": {
304
+ kind: "afs:directory",
305
+ operations: ["list"]
306
+ },
307
+ "/cameras/{camera}": {
308
+ kind: "frigate:camera",
309
+ operations: [
310
+ "list",
311
+ "read",
312
+ "exec"
313
+ ],
314
+ actions: [
315
+ "detect-on",
316
+ "detect-off",
317
+ "recordings-on",
318
+ "recordings-off",
319
+ "snapshots-on",
320
+ "snapshots-off",
321
+ "motion-on",
322
+ "motion-off",
323
+ "create-event"
324
+ ]
325
+ },
326
+ "/cameras/{camera}/events": {
327
+ kind: "afs:directory",
328
+ operations: ["list"]
329
+ },
330
+ "/cameras/{camera}/events/{eventId}": {
331
+ kind: "frigate:detection-event",
332
+ operations: ["read"]
333
+ },
334
+ "/cameras/{camera}/recordings": {
335
+ kind: "afs:directory",
336
+ operations: ["list"]
337
+ },
338
+ "/cameras/{camera}/reviews": {
339
+ kind: "afs:directory",
340
+ operations: ["list"]
341
+ },
342
+ "/events": {
343
+ kind: "afs:directory",
344
+ operations: ["list", "search"]
345
+ },
346
+ "/events/{eventId}": {
347
+ kind: "frigate:detection-event",
348
+ operations: [
349
+ "read",
350
+ "exec",
351
+ "delete"
352
+ ],
353
+ actions: [
354
+ "retain",
355
+ "unretain",
356
+ "set-sub-label",
357
+ "set-description",
358
+ "regenerate-description"
359
+ ],
360
+ destructive: ["delete"]
361
+ },
362
+ "/reviews": {
363
+ kind: "afs:directory",
364
+ operations: ["list", "exec"],
365
+ actions: ["summarize"]
366
+ },
367
+ "/reviews/{reviewId}": {
368
+ kind: "frigate:review",
369
+ operations: ["read", "exec"],
370
+ actions: ["mark-viewed", "unmark-viewed"]
371
+ },
372
+ "/exports": {
373
+ kind: "afs:directory",
374
+ operations: ["list", "exec"]
375
+ },
376
+ "/exports/{exportId}": {
377
+ kind: "frigate:export",
378
+ operations: [
379
+ "read",
380
+ "exec",
381
+ "delete"
382
+ ],
383
+ actions: ["rename"],
384
+ destructive: ["delete"]
385
+ },
386
+ "/dashboard": {
387
+ kind: "frigate:dashboard",
388
+ operations: ["read"]
389
+ },
390
+ "/config": {
391
+ kind: "frigate:config",
392
+ operations: ["read", "write"]
393
+ },
394
+ "/logs": {
395
+ kind: "afs:directory",
396
+ operations: ["list"]
397
+ },
398
+ "/users": {
399
+ kind: "afs:directory",
400
+ operations: ["list"]
401
+ }
402
+ },
403
+ auth: {
404
+ type: "custom",
405
+ env: ["FRIGATE_URL"]
406
+ },
407
+ bestFor: [
408
+ "NVR surveillance",
409
+ "event detection",
410
+ "camera monitoring"
411
+ ],
412
+ notFor: ["file storage", "database queries"]
413
+ };
414
+ }
415
+ static async load({ config } = {}) {
416
+ return new AFSFrigate(await AFSFrigate.schema().parseAsync(config));
417
+ }
418
+ name;
419
+ description;
420
+ accessMode;
421
+ client;
422
+ parsedOptions;
423
+ constructor(options) {
424
+ super();
425
+ this.parsedOptions = afsFrigateOptionsSchema.parse(options);
426
+ this.name = this.parsedOptions.name ?? "frigate";
427
+ this.description = this.parsedOptions.description;
428
+ this.accessMode = this.parsedOptions.accessMode ?? "readwrite";
429
+ this.client = new FrigateHttpClient({
430
+ url: this.parsedOptions.url,
431
+ username: this.parsedOptions.username,
432
+ password: this.parsedOptions.password,
433
+ cacheTtl: this.parsedOptions.cacheTtl
434
+ });
435
+ if (this.parsedOptions.mqttUrl) createMqttManager(this.parsedOptions.mqttUrl, this.parsedOptions.mqttTopicPrefix ?? "frigate", (topic) => this.handleMqttInvalidation(topic));
436
+ }
437
+ handleMqttInvalidation(topic) {
438
+ if (topic.includes("events")) this.client.invalidateCache("/api/events");
439
+ else if (topic.includes("reviews") || topic.includes("review")) this.client.invalidateCache("/api/review");
440
+ else if (topic.includes("stats")) this.client.invalidateCache("/api/stats");
441
+ else this.client.invalidateCache("/api/config");
442
+ }
443
+ async readCameraEventsMeta(ctx) {
444
+ return this.buildMetaForPath((0, ufo.joinURL)("/cameras", ctx.params.camera, "events"));
445
+ }
446
+ async readCameraMeta(ctx) {
447
+ return this.buildMetaForPath((0, ufo.joinURL)("/cameras", ctx.params.camera));
448
+ }
449
+ async readMetaHandler(ctx) {
450
+ const nodePath = (0, ufo.joinURL)("/", ctx.params.path ?? "");
451
+ return this.buildMetaForPath(nodePath);
452
+ }
453
+ async buildMetaForPath(nodePath) {
454
+ const metaPath = (0, ufo.joinURL)(nodePath, ".meta");
455
+ let meta;
456
+ try {
457
+ const pathParam = nodePath === "/" ? void 0 : nodePath.slice(1);
458
+ meta = (await this.statHandler({
459
+ path: nodePath,
460
+ params: { path: pathParam },
461
+ options: {}
462
+ })).data?.meta ?? {};
463
+ } catch {
464
+ throw new _aigne_afs.AFSNotFoundError(nodePath);
465
+ }
466
+ return {
467
+ id: metaPath,
468
+ path: metaPath,
469
+ content: meta,
470
+ meta
471
+ };
472
+ }
473
+ async readCapabilities(_ctx) {
474
+ const operations = this.getOperationsDeclaration();
475
+ const actions = [];
476
+ if (this.accessMode === "readwrite") actions.push({
477
+ kind: "frigate:detection-event",
478
+ description: "Event management actions",
479
+ catalog: [
480
+ {
481
+ name: "retain",
482
+ description: "Mark event for indefinite retention"
483
+ },
484
+ {
485
+ name: "unretain",
486
+ description: "Remove retention flag"
487
+ },
488
+ {
489
+ name: "delete",
490
+ description: "Delete event"
491
+ },
492
+ {
493
+ name: "set-sub-label",
494
+ description: "Set sub-label",
495
+ inputSchema: {
496
+ type: "object",
497
+ properties: { subLabel: { type: "string" } },
498
+ required: ["subLabel"]
499
+ }
500
+ },
501
+ {
502
+ name: "set-description",
503
+ description: "Set description",
504
+ inputSchema: {
505
+ type: "object",
506
+ properties: { description: { type: "string" } },
507
+ required: ["description"]
508
+ }
509
+ },
510
+ {
511
+ name: "regenerate-description",
512
+ description: "AI regenerate description"
513
+ }
514
+ ],
515
+ discovery: { pathTemplate: "/events/:id/.actions" }
516
+ }, {
517
+ kind: "frigate:camera",
518
+ description: "Camera control actions",
519
+ catalog: [
520
+ {
521
+ name: "detect-on",
522
+ description: "Enable object detection"
523
+ },
524
+ {
525
+ name: "detect-off",
526
+ description: "Disable object detection"
527
+ },
528
+ {
529
+ name: "recordings-on",
530
+ description: "Enable recording"
531
+ },
532
+ {
533
+ name: "recordings-off",
534
+ description: "Disable recording"
535
+ },
536
+ {
537
+ name: "snapshots-on",
538
+ description: "Enable snapshots"
539
+ },
540
+ {
541
+ name: "snapshots-off",
542
+ description: "Disable snapshots"
543
+ },
544
+ {
545
+ name: "motion-on",
546
+ description: "Enable motion detection"
547
+ },
548
+ {
549
+ name: "motion-off",
550
+ description: "Disable motion detection"
551
+ },
552
+ {
553
+ name: "create-event",
554
+ description: "Create manual event"
555
+ },
556
+ {
557
+ name: "export",
558
+ description: "Export recording range"
559
+ },
560
+ {
561
+ name: "ptz",
562
+ description: "PTZ control"
563
+ },
564
+ {
565
+ name: "notifications-on",
566
+ description: "Enable notifications"
567
+ },
568
+ {
569
+ name: "notifications-off",
570
+ description: "Disable notifications"
571
+ }
572
+ ],
573
+ discovery: { pathTemplate: "/cameras/:name/.actions" }
574
+ }, {
575
+ kind: "frigate:review",
576
+ description: "Review actions",
577
+ catalog: [{
578
+ name: "mark-viewed",
579
+ description: "Mark review as viewed"
580
+ }, {
581
+ name: "unmark-viewed",
582
+ description: "Unmark review as viewed"
583
+ }],
584
+ discovery: { pathTemplate: "/reviews/:id/.actions" }
585
+ }, {
586
+ kind: "frigate:export",
587
+ description: "Export actions",
588
+ catalog: [{
589
+ name: "delete",
590
+ description: "Delete export"
591
+ }, {
592
+ name: "rename",
593
+ description: "Rename export"
594
+ }],
595
+ discovery: { pathTemplate: "/exports/:id/.actions" }
596
+ });
597
+ return {
598
+ id: "/.meta/.capabilities",
599
+ path: "/.meta/.capabilities",
600
+ content: {
601
+ schemaVersion: 1,
602
+ provider: "frigate",
603
+ description: "Frigate NVR — cameras, events, and recordings as filesystem",
604
+ operations,
605
+ tools: [],
606
+ actions
607
+ },
608
+ meta: { kind: "afs:capabilities" }
609
+ };
610
+ }
611
+ async listRoot(_ctx) {
612
+ const config = await this.client.getConfig();
613
+ const cameraCount = Object.keys(config.cameras).length;
614
+ return { data: [
615
+ this.buildEntry("/cameras", { meta: {
616
+ kind: "afs:directory",
617
+ childrenCount: cameraCount,
618
+ description: "Frigate cameras"
619
+ } }),
620
+ this.buildEntry("/events", { meta: {
621
+ kind: "afs:directory",
622
+ childrenCount: -1,
623
+ description: "Detection events"
624
+ } }),
625
+ this.buildEntry("/reviews", { meta: {
626
+ kind: "afs:directory",
627
+ childrenCount: -1,
628
+ description: "Review segments"
629
+ } }),
630
+ this.buildEntry("/exports", { meta: {
631
+ kind: "afs:directory",
632
+ childrenCount: -1,
633
+ description: "Exported clips"
634
+ } }),
635
+ this.buildEntry("/dashboard", { meta: {
636
+ kind: "frigate:dashboard",
637
+ childrenCount: 0,
638
+ description: "System dashboard"
639
+ } }),
640
+ this.buildEntry("/config", { meta: {
641
+ kind: "frigate:config",
642
+ childrenCount: 0,
643
+ description: "Frigate configuration"
644
+ } }),
645
+ this.buildEntry("/logs", { meta: {
646
+ kind: "afs:directory",
647
+ childrenCount: 3,
648
+ description: "Service logs"
649
+ } }),
650
+ this.buildEntry("/users", { meta: {
651
+ kind: "afs:directory",
652
+ childrenCount: -1,
653
+ description: "User management"
654
+ } })
655
+ ] };
656
+ }
657
+ async readRoot(_ctx) {
658
+ return this.buildEntry("/", {
659
+ content: "Frigate NVR instance",
660
+ meta: {
661
+ kind: "frigate:nvr",
662
+ childrenCount: 8,
663
+ description: "Frigate NVR instance"
664
+ }
665
+ });
666
+ }
667
+ async listCameras(_ctx) {
668
+ const config = await this.client.getConfig();
669
+ return { data: Object.keys(config.cameras).map((name) => {
670
+ const cam = config.cameras[name];
671
+ const description = `${this.formatCameraName(name)} camera`;
672
+ return this.buildEntry((0, ufo.joinURL)("/cameras", name), {
673
+ id: name,
674
+ meta: {
675
+ kind: "frigate:camera",
676
+ id: name,
677
+ childrenCount: 3,
678
+ description,
679
+ detect: cam.detect,
680
+ record: cam.record,
681
+ snapshots: cam.snapshots
682
+ }
683
+ });
684
+ }) };
685
+ }
686
+ async readCamerasDir(_ctx) {
687
+ const config = await this.client.getConfig();
688
+ const count = Object.keys(config.cameras).length;
689
+ return this.buildEntry("/cameras", { meta: {
690
+ kind: "afs:directory",
691
+ childrenCount: count
692
+ } });
693
+ }
694
+ async listCamera(ctx) {
695
+ const cameraName = ctx.params.camera;
696
+ if (isPathTraversal(cameraName)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
697
+ if (!(await this.client.getConfig()).cameras[cameraName]) throw new _aigne_afs.AFSNotFoundError(ctx.path);
698
+ return { data: [
699
+ this.buildEntry((0, ufo.joinURL)("/cameras", cameraName, "events"), { meta: {
700
+ kind: "afs:directory",
701
+ childrenCount: -1,
702
+ description: `Events for ${cameraName}`
703
+ } }),
704
+ this.buildEntry((0, ufo.joinURL)("/cameras", cameraName, "recordings"), { meta: {
705
+ kind: "afs:directory",
706
+ childrenCount: -1,
707
+ description: `Recordings for ${cameraName}`
708
+ } }),
709
+ this.buildEntry((0, ufo.joinURL)("/cameras", cameraName, "reviews"), { meta: {
710
+ kind: "afs:directory",
711
+ childrenCount: -1,
712
+ description: `Reviews for ${cameraName}`
713
+ } })
714
+ ] };
715
+ }
716
+ async readCamera(ctx) {
717
+ const cameraName = ctx.params.camera;
718
+ if (isPathTraversal(cameraName)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
719
+ const cam = (await this.client.getConfig()).cameras[cameraName];
720
+ if (!cam) throw new _aigne_afs.AFSNotFoundError(ctx.path);
721
+ const lines = [];
722
+ lines.push(`Camera: ${cameraName}`);
723
+ if (cam.detect) {
724
+ lines.push(`Resolution: ${cam.detect.width}x${cam.detect.height} @ ${cam.detect.fps} FPS`);
725
+ lines.push(`Detection: ${cam.detect.enabled ? "enabled" : "disabled"}`);
726
+ }
727
+ if (cam.record) lines.push(`Recording: ${cam.record.enabled ? "enabled" : "disabled"}`);
728
+ if (cam.snapshots) lines.push(`Snapshots: ${cam.snapshots.enabled ? "enabled" : "disabled"}`);
729
+ if (cam.zones && Object.keys(cam.zones).length > 0) lines.push(`Zones: ${Object.keys(cam.zones).join(", ")}`);
730
+ return this.buildEntry(ctx.path, {
731
+ id: cameraName,
732
+ content: lines.join("\n"),
733
+ meta: {
734
+ kind: "frigate:camera",
735
+ id: cameraName,
736
+ childrenCount: 3,
737
+ description: `${this.formatCameraName(cameraName)} camera`
738
+ }
739
+ });
740
+ }
741
+ async listCameraEvents(ctx) {
742
+ const cameraName = ctx.params.camera;
743
+ if (isPathTraversal(cameraName)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
744
+ if (!(await this.client.getConfig()).cameras[cameraName]) throw new _aigne_afs.AFSNotFoundError(ctx.path);
745
+ const events = await this.client.getEvents({ camera: cameraName });
746
+ return { data: this.eventsToEntries(events, (0, ufo.joinURL)("/cameras", cameraName, "events")) };
747
+ }
748
+ async readCameraEventsDir(ctx) {
749
+ const cameraName = ctx.params.camera;
750
+ if (isPathTraversal(cameraName)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
751
+ if (!(await this.client.getConfig()).cameras[cameraName]) throw new _aigne_afs.AFSNotFoundError(ctx.path);
752
+ return this.buildEntry(ctx.path, { meta: {
753
+ kind: "afs:directory",
754
+ childrenCount: -1,
755
+ description: `Events for ${cameraName}`
756
+ } });
757
+ }
758
+ async readCameraEvent(ctx) {
759
+ const { camera, eventId } = ctx.params;
760
+ if (isPathTraversal(camera) || isPathTraversal(eventId)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
761
+ try {
762
+ const event = await this.client.getEvent(eventId);
763
+ if (event.camera !== camera) throw new _aigne_afs.AFSNotFoundError(ctx.path);
764
+ return this.eventToEntry(event, (0, ufo.joinURL)("/cameras", camera, "events"));
765
+ } catch (error) {
766
+ if (error instanceof _aigne_afs.AFSNotFoundError) throw error;
767
+ if (error instanceof FrigateApiError && error.code === "AFS_NOT_FOUND") throw new _aigne_afs.AFSNotFoundError(ctx.path);
768
+ throw this.mapFrigateError(error, ctx.path);
769
+ }
770
+ }
771
+ async readCameraLatest(ctx) {
772
+ const cameraName = ctx.params.camera;
773
+ if (isPathTraversal(cameraName)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
774
+ if (!(await this.client.getConfig()).cameras[cameraName]) throw new _aigne_afs.AFSNotFoundError(ctx.path);
775
+ try {
776
+ const bytes = await this.client.fetchBinary(`/api/${cameraName}/latest.jpg`);
777
+ const base64 = this.arrayBufferToBase64(bytes);
778
+ return this.buildEntry(ctx.path, {
779
+ content: base64,
780
+ meta: {
781
+ kind: "frigate:image",
782
+ contentType: "image/jpeg",
783
+ encoding: "base64",
784
+ childrenCount: 0
785
+ }
786
+ });
787
+ } catch (error) {
788
+ if (error instanceof FrigateApiError && error.code === "AFS_NOT_FOUND") throw new _aigne_afs.AFSNotFoundError(ctx.path);
789
+ throw this.mapFrigateError(error, ctx.path);
790
+ }
791
+ }
792
+ async readCameraGrid(ctx) {
793
+ const cameraName = ctx.params.camera;
794
+ if (isPathTraversal(cameraName)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
795
+ if (!(await this.client.getConfig()).cameras[cameraName]) throw new _aigne_afs.AFSNotFoundError(ctx.path);
796
+ try {
797
+ const bytes = await this.client.fetchBinary(`/api/${cameraName}/grid.jpg`);
798
+ const base64 = this.arrayBufferToBase64(bytes);
799
+ return this.buildEntry(ctx.path, {
800
+ content: base64,
801
+ meta: {
802
+ kind: "frigate:image",
803
+ contentType: "image/jpeg",
804
+ encoding: "base64",
805
+ childrenCount: 0
806
+ }
807
+ });
808
+ } catch (error) {
809
+ if (error instanceof FrigateApiError && error.code === "AFS_NOT_FOUND") throw new _aigne_afs.AFSNotFoundError(ctx.path);
810
+ throw this.mapFrigateError(error, ctx.path);
811
+ }
812
+ }
813
+ async readCameraBestSnapshot(ctx) {
814
+ const { camera, label } = ctx.params;
815
+ if (isPathTraversal(camera) || isPathTraversal(label)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
816
+ if (!(await this.client.getConfig()).cameras[camera]) throw new _aigne_afs.AFSNotFoundError(ctx.path);
817
+ try {
818
+ const bytes = await this.client.fetchBinary(`/api/${camera}/${label}/best.jpg`);
819
+ const base64 = this.arrayBufferToBase64(bytes);
820
+ return this.buildEntry(ctx.path, {
821
+ content: base64,
822
+ meta: {
823
+ kind: "frigate:image",
824
+ contentType: "image/jpeg",
825
+ encoding: "base64",
826
+ childrenCount: 0
827
+ }
828
+ });
829
+ } catch (error) {
830
+ if (error instanceof FrigateApiError && error.code === "AFS_NOT_FOUND") throw new _aigne_afs.AFSNotFoundError(ctx.path);
831
+ throw this.mapFrigateError(error, ctx.path);
832
+ }
833
+ }
834
+ async readCameraPtz(ctx) {
835
+ const cameraName = ctx.params.camera;
836
+ if (isPathTraversal(cameraName)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
837
+ if (!(await this.client.getConfig()).cameras[cameraName]) throw new _aigne_afs.AFSNotFoundError(ctx.path);
838
+ try {
839
+ const ptzInfo = await this.client.getPtzInfo(cameraName);
840
+ const lines = [];
841
+ lines.push(`PTZ Info for ${cameraName}`);
842
+ lines.push(`Features: ${ptzInfo.features.join(", ") || "none"}`);
843
+ lines.push(`Presets: ${ptzInfo.presets.join(", ") || "none"}`);
844
+ return this.buildEntry(ctx.path, {
845
+ content: lines.join("\n"),
846
+ meta: {
847
+ kind: "frigate:ptz",
848
+ childrenCount: 0,
849
+ features: ptzInfo.features,
850
+ presets: ptzInfo.presets
851
+ }
852
+ });
853
+ } catch (error) {
854
+ if (error instanceof FrigateApiError && error.code === "AFS_NOT_FOUND") throw new _aigne_afs.AFSNotFoundError(ctx.path);
855
+ throw this.mapFrigateError(error, ctx.path);
856
+ }
857
+ }
858
+ async listCameraRecordings(ctx) {
859
+ const cameraName = ctx.params.camera;
860
+ if (isPathTraversal(cameraName)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
861
+ if (!(await this.client.getConfig()).cameras[cameraName]) throw new _aigne_afs.AFSNotFoundError(ctx.path);
862
+ try {
863
+ return { data: (await this.client.getRecordings(cameraName)).map((rec) => this.buildEntry((0, ufo.joinURL)("/cameras", cameraName, "recordings", rec.id), {
864
+ id: rec.id,
865
+ meta: {
866
+ kind: "frigate:recording",
867
+ childrenCount: 0,
868
+ camera: cameraName,
869
+ startTime: rec.start_time,
870
+ endTime: rec.end_time,
871
+ duration: rec.duration,
872
+ motion: rec.motion,
873
+ objects: rec.objects
874
+ }
875
+ })) };
876
+ } catch (error) {
877
+ if (error instanceof FrigateApiError && error.code === "AFS_NOT_FOUND") throw new _aigne_afs.AFSNotFoundError(ctx.path);
878
+ throw this.mapFrigateError(error, ctx.path);
879
+ }
880
+ }
881
+ async readCameraRecordingsDir(ctx) {
882
+ const cameraName = ctx.params.camera;
883
+ if (isPathTraversal(cameraName)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
884
+ if (!(await this.client.getConfig()).cameras[cameraName]) throw new _aigne_afs.AFSNotFoundError(ctx.path);
885
+ return this.buildEntry(ctx.path, { meta: {
886
+ kind: "afs:directory",
887
+ childrenCount: -1,
888
+ description: `Recordings for ${cameraName}`
889
+ } });
890
+ }
891
+ async readCameraRecordingsSummary(ctx) {
892
+ const cameraName = ctx.params.camera;
893
+ if (isPathTraversal(cameraName)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
894
+ if (!(await this.client.getConfig()).cameras[cameraName]) throw new _aigne_afs.AFSNotFoundError(ctx.path);
895
+ try {
896
+ const summary = await this.client.getRecordingsSummary(cameraName);
897
+ return this.buildEntry(ctx.path, {
898
+ content: summary,
899
+ meta: {
900
+ kind: "frigate:recording-summary",
901
+ childrenCount: 0
902
+ }
903
+ });
904
+ } catch (error) {
905
+ if (error instanceof FrigateApiError && error.code === "AFS_NOT_FOUND") throw new _aigne_afs.AFSNotFoundError(ctx.path);
906
+ throw this.mapFrigateError(error, ctx.path);
907
+ }
908
+ }
909
+ async readRecordingSnapshot(ctx) {
910
+ const { camera, time } = ctx.params;
911
+ if (isPathTraversal(camera) || isPathTraversal(time)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
912
+ if (!(await this.client.getConfig()).cameras[camera]) throw new _aigne_afs.AFSNotFoundError(ctx.path);
913
+ try {
914
+ const bytes = await this.client.fetchBinary(`/api/${camera}/recordings/${time}/snapshot.png`);
915
+ const base64 = this.arrayBufferToBase64(bytes);
916
+ return this.buildEntry(ctx.path, {
917
+ content: base64,
918
+ meta: {
919
+ kind: "frigate:image",
920
+ contentType: "image/png",
921
+ encoding: "base64",
922
+ childrenCount: 0
923
+ }
924
+ });
925
+ } catch (error) {
926
+ if (error instanceof FrigateApiError && error.code === "AFS_NOT_FOUND") throw new _aigne_afs.AFSNotFoundError(ctx.path);
927
+ throw this.mapFrigateError(error, ctx.path);
928
+ }
929
+ }
930
+ async listCameraReviews(ctx) {
931
+ const cameraName = ctx.params.camera;
932
+ if (isPathTraversal(cameraName)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
933
+ if (!(await this.client.getConfig()).cameras[cameraName]) throw new _aigne_afs.AFSNotFoundError(ctx.path);
934
+ try {
935
+ const reviews = await this.client.getReviews({ camera: cameraName });
936
+ return { data: this.reviewsToEntries(reviews) };
937
+ } catch (error) {
938
+ if (error instanceof FrigateApiError && error.code === "AFS_NOT_FOUND") throw new _aigne_afs.AFSNotFoundError(ctx.path);
939
+ throw this.mapFrigateError(error, ctx.path);
940
+ }
941
+ }
942
+ async readCameraReviewsDir(ctx) {
943
+ const cameraName = ctx.params.camera;
944
+ if (isPathTraversal(cameraName)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
945
+ if (!(await this.client.getConfig()).cameras[cameraName]) throw new _aigne_afs.AFSNotFoundError(ctx.path);
946
+ return this.buildEntry(ctx.path, { meta: {
947
+ kind: "afs:directory",
948
+ childrenCount: -1,
949
+ description: `Reviews for ${cameraName}`
950
+ } });
951
+ }
952
+ async listEvents(ctx) {
953
+ const options = ctx.options;
954
+ const params = {};
955
+ if (options?.limit) params.limit = String(options.limit);
956
+ const events = await this.client.getEvents(params);
957
+ return { data: this.eventsToEntries(events, "/events") };
958
+ }
959
+ async readEventsDir(_ctx) {
960
+ return this.buildEntry("/events", { meta: {
961
+ kind: "afs:directory",
962
+ childrenCount: -1
963
+ } });
964
+ }
965
+ async readEventsSummary(_ctx) {
966
+ const lines = (await this.client.getEventsSummary()).map((s) => `${s.camera}: ${s.label} x${s.count} (${s.day})`);
967
+ return this.buildEntry("/events/summary", {
968
+ content: lines.join("\n"),
969
+ meta: {
970
+ kind: "frigate:summary",
971
+ childrenCount: 0
972
+ }
973
+ });
974
+ }
975
+ async readEvent(ctx) {
976
+ const eventId = ctx.params.eventId;
977
+ if (isPathTraversal(eventId)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
978
+ try {
979
+ const event = await this.client.getEvent(eventId);
980
+ return this.eventToEntry(event, "/events");
981
+ } catch (error) {
982
+ if (error instanceof FrigateApiError && error.code === "AFS_NOT_FOUND") throw new _aigne_afs.AFSNotFoundError(ctx.path);
983
+ throw this.mapFrigateError(error, ctx.path);
984
+ }
985
+ }
986
+ async readEventSnapshot(ctx) {
987
+ const eventId = ctx.params.eventId;
988
+ if (isPathTraversal(eventId)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
989
+ try {
990
+ if (!(await this.client.getEvent(eventId)).has_snapshot) throw new _aigne_afs.AFSNotFoundError(ctx.path);
991
+ const bytes = await this.client.fetchBinary(`/api/events/${eventId}/snapshot.jpg`);
992
+ const base64 = this.arrayBufferToBase64(bytes);
993
+ return this.buildEntry(ctx.path, {
994
+ id: `${eventId}/snapshot`,
995
+ content: base64,
996
+ meta: {
997
+ kind: "frigate:image",
998
+ contentType: "image/jpeg",
999
+ encoding: "base64",
1000
+ childrenCount: 0
1001
+ }
1002
+ });
1003
+ } catch (error) {
1004
+ if (error instanceof _aigne_afs.AFSNotFoundError) throw error;
1005
+ if (error instanceof FrigateApiError && error.code === "AFS_NOT_FOUND") throw new _aigne_afs.AFSNotFoundError(ctx.path);
1006
+ throw this.mapFrigateError(error, ctx.path);
1007
+ }
1008
+ }
1009
+ async readEventThumbnail(ctx) {
1010
+ const eventId = ctx.params.eventId;
1011
+ if (isPathTraversal(eventId)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
1012
+ try {
1013
+ await this.client.getEvent(eventId);
1014
+ const bytes = await this.client.fetchBinary(`/api/events/${eventId}/thumbnail.jpg`);
1015
+ const base64 = this.arrayBufferToBase64(bytes);
1016
+ return this.buildEntry(ctx.path, {
1017
+ id: `${eventId}/thumbnail`,
1018
+ content: base64,
1019
+ meta: {
1020
+ kind: "frigate:image",
1021
+ contentType: "image/jpeg",
1022
+ encoding: "base64",
1023
+ childrenCount: 0
1024
+ }
1025
+ });
1026
+ } catch (error) {
1027
+ if (error instanceof _aigne_afs.AFSNotFoundError) throw error;
1028
+ if (error instanceof FrigateApiError && error.code === "AFS_NOT_FOUND") throw new _aigne_afs.AFSNotFoundError(ctx.path);
1029
+ throw this.mapFrigateError(error, ctx.path);
1030
+ }
1031
+ }
1032
+ async readEventClip(ctx) {
1033
+ const eventId = ctx.params.eventId;
1034
+ if (isPathTraversal(eventId)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
1035
+ try {
1036
+ if (!(await this.client.getEvent(eventId)).has_clip) throw new _aigne_afs.AFSNotFoundError(ctx.path);
1037
+ const clipUrl = `${this.client.getBaseUrl()}/api/events/${eventId}/clip.mp4`;
1038
+ return this.buildEntry(ctx.path, {
1039
+ id: `${eventId}/clip`,
1040
+ content: clipUrl,
1041
+ meta: {
1042
+ kind: "frigate:video-clip",
1043
+ contentType: "video/mp4",
1044
+ encoding: "url",
1045
+ childrenCount: 0
1046
+ }
1047
+ });
1048
+ } catch (error) {
1049
+ if (error instanceof _aigne_afs.AFSNotFoundError) throw error;
1050
+ if (error instanceof FrigateApiError && error.code === "AFS_NOT_FOUND") throw new _aigne_afs.AFSNotFoundError(ctx.path);
1051
+ throw this.mapFrigateError(error, ctx.path);
1052
+ }
1053
+ }
1054
+ async listReviews(ctx) {
1055
+ const options = ctx.options;
1056
+ const params = {};
1057
+ if (options?.limit) params.limit = String(options.limit);
1058
+ if (options?.severity) params.severity = options.severity;
1059
+ const reviews = await this.client.getReviews(params);
1060
+ return { data: this.reviewsToEntries(reviews) };
1061
+ }
1062
+ async readReviewsDir(_ctx) {
1063
+ return this.buildEntry("/reviews", { meta: {
1064
+ kind: "afs:directory",
1065
+ childrenCount: -1
1066
+ } });
1067
+ }
1068
+ async readReviewsSummary(_ctx) {
1069
+ const summary = await this.client.getReviewSummary();
1070
+ return this.buildEntry("/reviews/summary", {
1071
+ content: summary,
1072
+ meta: {
1073
+ kind: "frigate:summary",
1074
+ childrenCount: 0
1075
+ }
1076
+ });
1077
+ }
1078
+ async readReview(ctx) {
1079
+ const reviewId = ctx.params.reviewId;
1080
+ if (isPathTraversal(reviewId)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
1081
+ try {
1082
+ const review = await this.client.getReview(reviewId);
1083
+ return this.reviewToEntry(review);
1084
+ } catch (error) {
1085
+ if (error instanceof FrigateApiError && error.code === "AFS_NOT_FOUND") throw new _aigne_afs.AFSNotFoundError(ctx.path);
1086
+ throw this.mapFrigateError(error, ctx.path);
1087
+ }
1088
+ }
1089
+ async listExports(_ctx) {
1090
+ return { data: (await this.client.getExports()).map((exp) => this.buildEntry((0, ufo.joinURL)("/exports", exp.id), {
1091
+ id: exp.id,
1092
+ meta: {
1093
+ kind: "frigate:export",
1094
+ childrenCount: 0,
1095
+ camera: exp.camera,
1096
+ name: exp.name,
1097
+ date: exp.date,
1098
+ inProgress: exp.in_progress
1099
+ }
1100
+ })) };
1101
+ }
1102
+ async readExportsDir(_ctx) {
1103
+ return this.buildEntry("/exports", { meta: {
1104
+ kind: "afs:directory",
1105
+ childrenCount: -1
1106
+ } });
1107
+ }
1108
+ async readExport(ctx) {
1109
+ const exportId = ctx.params.exportId;
1110
+ if (isPathTraversal(exportId)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
1111
+ try {
1112
+ const exp = await this.client.getExport(exportId);
1113
+ return this.buildEntry(ctx.path, {
1114
+ id: exp.id,
1115
+ content: `Export: ${exp.name}\nCamera: ${exp.camera}\nStatus: ${exp.in_progress ? "in_progress" : "complete"}`,
1116
+ meta: {
1117
+ kind: "frigate:export",
1118
+ childrenCount: 0,
1119
+ camera: exp.camera,
1120
+ name: exp.name,
1121
+ date: exp.date,
1122
+ inProgress: exp.in_progress
1123
+ }
1124
+ });
1125
+ } catch (error) {
1126
+ if (error instanceof FrigateApiError && error.code === "AFS_NOT_FOUND") throw new _aigne_afs.AFSNotFoundError(ctx.path);
1127
+ throw this.mapFrigateError(error, ctx.path);
1128
+ }
1129
+ }
1130
+ async readDashboard(_ctx) {
1131
+ const stats = await this.client.getStats();
1132
+ const lines = [];
1133
+ lines.push("Frigate NVR Status");
1134
+ lines.push("");
1135
+ lines.push(`Version: ${stats.service.version}`);
1136
+ lines.push(`Uptime: ${this.formatUptime(stats.service.uptime)}`);
1137
+ lines.push("");
1138
+ const detectorEntries = Object.entries(stats.detectors);
1139
+ if (detectorEntries.length > 0) {
1140
+ lines.push("Detectors:");
1141
+ for (const [name, det] of detectorEntries) lines.push(` ${name}: inference ${det.inference_speed}ms`);
1142
+ lines.push("");
1143
+ }
1144
+ const cameraEntries = Object.entries(stats.cameras);
1145
+ if (cameraEntries.length > 0) {
1146
+ lines.push("Cameras:");
1147
+ for (const [name, cam] of cameraEntries) lines.push(` ${name}: ${cam.camera_fps} fps, detection ${cam.detection_fps} fps`);
1148
+ }
1149
+ return this.buildEntry("/dashboard", {
1150
+ content: lines.join("\n"),
1151
+ meta: {
1152
+ kind: "frigate:dashboard",
1153
+ childrenCount: 0
1154
+ }
1155
+ });
1156
+ }
1157
+ async readConfig(_ctx) {
1158
+ const config = await this.client.getConfig();
1159
+ const cameraNames = Object.keys(config.cameras);
1160
+ const lines = [];
1161
+ lines.push("Frigate configuration summary");
1162
+ lines.push("");
1163
+ lines.push(`Cameras: ${cameraNames.length}`);
1164
+ for (const name of cameraNames) {
1165
+ const cam = config.cameras[name];
1166
+ const features = [
1167
+ cam.detect?.enabled ? "detect" : "",
1168
+ cam.record?.enabled ? "record" : "",
1169
+ cam.snapshots?.enabled ? "snapshot" : ""
1170
+ ].filter(Boolean).join(", ");
1171
+ lines.push(` ${name}: ${features || "none"}`);
1172
+ }
1173
+ return this.buildEntry("/config", {
1174
+ content: lines.join("\n"),
1175
+ meta: {
1176
+ kind: "frigate:config",
1177
+ childrenCount: 0
1178
+ }
1179
+ });
1180
+ }
1181
+ async readConfigRaw(_ctx) {
1182
+ try {
1183
+ const rawConfig = await this.client.fetchText("/api/config/raw");
1184
+ return this.buildEntry("/config/raw", {
1185
+ content: rawConfig,
1186
+ meta: {
1187
+ kind: "frigate:config",
1188
+ childrenCount: 0,
1189
+ contentType: "text/yaml"
1190
+ }
1191
+ });
1192
+ } catch (error) {
1193
+ throw this.mapFrigateError(error, "/config/raw");
1194
+ }
1195
+ }
1196
+ async writeConfig(ctx, payload) {
1197
+ const content = typeof payload.content === "string" ? payload.content : JSON.stringify(payload.content);
1198
+ if (!content || content.trim() === "") throw new Error("Config content cannot be empty");
1199
+ try {
1200
+ await this.client.postJSON("/api/config/save", { content });
1201
+ } catch {}
1202
+ try {
1203
+ if (!(await fetch((0, ufo.joinURL)(this.client.getBaseUrl(), "/api/config/save"), {
1204
+ method: "POST",
1205
+ headers: { "Content-Type": "text/plain" },
1206
+ body: content
1207
+ })).ok) throw new Error("Failed to save config");
1208
+ } catch (error) {
1209
+ throw this.mapFrigateError(error, ctx.path);
1210
+ }
1211
+ this.client.invalidateCache("/api/config");
1212
+ return { data: this.buildEntry("/config", {
1213
+ content,
1214
+ meta: {
1215
+ kind: "frigate:config",
1216
+ childrenCount: 0
1217
+ }
1218
+ }) };
1219
+ }
1220
+ async readLogsDir(_ctx) {
1221
+ return this.buildEntry("/logs", { meta: {
1222
+ kind: "afs:directory",
1223
+ childrenCount: 3,
1224
+ description: "Service logs"
1225
+ } });
1226
+ }
1227
+ async listLogs(_ctx) {
1228
+ return { data: [
1229
+ this.buildEntry("/logs/frigate", { meta: {
1230
+ kind: "frigate:log",
1231
+ childrenCount: 0,
1232
+ description: "Frigate service logs"
1233
+ } }),
1234
+ this.buildEntry("/logs/nginx", { meta: {
1235
+ kind: "frigate:log",
1236
+ childrenCount: 0,
1237
+ description: "Nginx logs"
1238
+ } }),
1239
+ this.buildEntry("/logs/go2rtc", { meta: {
1240
+ kind: "frigate:log",
1241
+ childrenCount: 0,
1242
+ description: "go2rtc logs"
1243
+ } })
1244
+ ] };
1245
+ }
1246
+ async readLog(ctx) {
1247
+ const service = ctx.params.service;
1248
+ if (![
1249
+ "frigate",
1250
+ "nginx",
1251
+ "go2rtc"
1252
+ ].includes(service)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
1253
+ try {
1254
+ const logContent = await this.client.fetchText(`/api/logs/${service}`);
1255
+ return this.buildEntry(ctx.path, {
1256
+ content: logContent,
1257
+ meta: {
1258
+ kind: "frigate:log",
1259
+ childrenCount: 0,
1260
+ service
1261
+ }
1262
+ });
1263
+ } catch (error) {
1264
+ if (error instanceof FrigateApiError && error.code === "AFS_NOT_FOUND") throw new _aigne_afs.AFSNotFoundError(ctx.path);
1265
+ throw this.mapFrigateError(error, ctx.path);
1266
+ }
1267
+ }
1268
+ async listUsers(_ctx) {
1269
+ try {
1270
+ return { data: (await this.client.getUsers()).map((user) => this.buildEntry((0, ufo.joinURL)("/users", user.username), {
1271
+ id: user.username,
1272
+ meta: {
1273
+ kind: "frigate:user",
1274
+ childrenCount: 0,
1275
+ username: user.username,
1276
+ role: user.role
1277
+ }
1278
+ })) };
1279
+ } catch (error) {
1280
+ throw this.mapFrigateError(error, "/users");
1281
+ }
1282
+ }
1283
+ async readUsersDir(_ctx) {
1284
+ return this.buildEntry("/users", { meta: {
1285
+ kind: "afs:directory",
1286
+ childrenCount: -1
1287
+ } });
1288
+ }
1289
+ async readUser(ctx) {
1290
+ const username = ctx.params.username;
1291
+ if (isPathTraversal(username)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
1292
+ try {
1293
+ const user = (await this.client.getUsers()).find((u) => u.username === username);
1294
+ if (!user) throw new _aigne_afs.AFSNotFoundError(ctx.path);
1295
+ return this.buildEntry(ctx.path, {
1296
+ id: user.username,
1297
+ content: `User: ${user.username}\nRole: ${user.role}`,
1298
+ meta: {
1299
+ kind: "frigate:user",
1300
+ childrenCount: 0,
1301
+ username: user.username,
1302
+ role: user.role
1303
+ }
1304
+ });
1305
+ } catch (error) {
1306
+ if (error instanceof _aigne_afs.AFSNotFoundError) throw error;
1307
+ throw this.mapFrigateError(error, ctx.path);
1308
+ }
1309
+ }
1310
+ async readRecordingsStorage(_ctx) {
1311
+ const storage = await this.client.getRecordingsStorage();
1312
+ const lines = ["Recording Storage"];
1313
+ for (const [cam, data] of Object.entries(storage)) {
1314
+ const s = data;
1315
+ lines.push(` ${cam}: ${s.usage}MB (${s.usage_percent}%), bandwidth: ${s.bandwidth}Mbps`);
1316
+ }
1317
+ return this.buildEntry("/recordings/storage", {
1318
+ content: lines.join("\n"),
1319
+ meta: {
1320
+ kind: "frigate:storage",
1321
+ childrenCount: 0
1322
+ }
1323
+ });
1324
+ }
1325
+ async listEventLeaf(ctx) {
1326
+ const eventId = ctx.params.eventId;
1327
+ if (isPathTraversal(eventId)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
1328
+ try {
1329
+ await this.client.getEvent(eventId);
1330
+ } catch (error) {
1331
+ if (error instanceof FrigateApiError && error.code === "AFS_NOT_FOUND") throw new _aigne_afs.AFSNotFoundError(ctx.path);
1332
+ throw this.mapFrigateError(error, ctx.path);
1333
+ }
1334
+ return { data: [] };
1335
+ }
1336
+ async listCameraEventLeaf(ctx) {
1337
+ const { camera, eventId } = ctx.params;
1338
+ if (isPathTraversal(camera) || isPathTraversal(eventId)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
1339
+ try {
1340
+ if ((await this.client.getEvent(eventId)).camera !== camera) throw new _aigne_afs.AFSNotFoundError(ctx.path);
1341
+ } catch (error) {
1342
+ if (error instanceof _aigne_afs.AFSNotFoundError) throw error;
1343
+ if (error instanceof FrigateApiError && error.code === "AFS_NOT_FOUND") throw new _aigne_afs.AFSNotFoundError(ctx.path);
1344
+ throw this.mapFrigateError(error, ctx.path);
1345
+ }
1346
+ return { data: [] };
1347
+ }
1348
+ async listDashboard(_ctx) {
1349
+ return { data: [] };
1350
+ }
1351
+ async listConfig(_ctx) {
1352
+ return { data: [] };
1353
+ }
1354
+ async listReviewLeaf(ctx) {
1355
+ const reviewId = ctx.params.reviewId;
1356
+ if (isPathTraversal(reviewId)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
1357
+ try {
1358
+ await this.client.getReview(reviewId);
1359
+ } catch (error) {
1360
+ if (error instanceof FrigateApiError && error.code === "AFS_NOT_FOUND") throw new _aigne_afs.AFSNotFoundError(ctx.path);
1361
+ throw this.mapFrigateError(error, ctx.path);
1362
+ }
1363
+ return { data: [] };
1364
+ }
1365
+ async listExportLeaf(ctx) {
1366
+ const exportId = ctx.params.exportId;
1367
+ if (isPathTraversal(exportId)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
1368
+ try {
1369
+ await this.client.getExport(exportId);
1370
+ } catch (error) {
1371
+ if (error instanceof FrigateApiError && error.code === "AFS_NOT_FOUND") throw new _aigne_afs.AFSNotFoundError(ctx.path);
1372
+ throw this.mapFrigateError(error, ctx.path);
1373
+ }
1374
+ return { data: [] };
1375
+ }
1376
+ async searchEvents(_ctx, query, options) {
1377
+ const meta = options?.meta;
1378
+ if (options?.type === "semantic" || meta?.semantic) try {
1379
+ const params$1 = {};
1380
+ if (query) params$1.query = query;
1381
+ const events$1 = await this.client.searchEvents(params$1);
1382
+ return { data: this.eventsToEntries(events$1, "/events") };
1383
+ } catch {
1384
+ return {
1385
+ data: [],
1386
+ message: "Semantic search unavailable"
1387
+ };
1388
+ }
1389
+ const params = {};
1390
+ if (query) params.label = query;
1391
+ if (meta?.camera) params.camera = meta.camera;
1392
+ if (meta?.zone) params.zone = meta.zone;
1393
+ if (meta?.minScore) params.min_score = String(meta.minScore);
1394
+ if (meta?.after) params.after = String(meta.after);
1395
+ if (meta?.before) params.before = String(meta.before);
1396
+ const events = await this.client.getEvents(params);
1397
+ return { data: this.eventsToEntries(events, "/events") };
1398
+ }
1399
+ async searchGeneric(_ctx, _query, _options) {
1400
+ return { data: [] };
1401
+ }
1402
+ async listEventActions(ctx) {
1403
+ const eventId = ctx.params.eventId;
1404
+ if (isPathTraversal(eventId)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
1405
+ const basePath = (0, ufo.joinURL)("/events", eventId, ".actions");
1406
+ return { data: [
1407
+ this.buildActionEntry(basePath, "retain", "Mark event for indefinite retention", void 0, "ambient"),
1408
+ this.buildActionEntry(basePath, "unretain", "Remove retention flag", void 0, "ambient"),
1409
+ this.buildActionEntry(basePath, "delete", "Delete event", void 0, "critical"),
1410
+ this.buildActionEntry(basePath, "set-sub-label", "Set sub-label", {
1411
+ type: "object",
1412
+ properties: { subLabel: { type: "string" } },
1413
+ required: ["subLabel"]
1414
+ }, "ambient"),
1415
+ this.buildActionEntry(basePath, "set-description", "Set description", {
1416
+ type: "object",
1417
+ properties: { description: { type: "string" } },
1418
+ required: ["description"]
1419
+ }, "ambient"),
1420
+ this.buildActionEntry(basePath, "regenerate-description", "AI regenerate description", void 0, "ambient")
1421
+ ] };
1422
+ }
1423
+ async retainEvent(ctx) {
1424
+ const eventId = ctx.params.eventId;
1425
+ if (isPathTraversal(eventId)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
1426
+ try {
1427
+ await this.client.postJSON(`/api/events/${eventId}/retain`);
1428
+ this.client.invalidateCache("/api/events");
1429
+ return {
1430
+ success: true,
1431
+ data: { message: "Event retained" }
1432
+ };
1433
+ } catch (error) {
1434
+ if (error instanceof FrigateApiError && error.code === "AFS_NOT_FOUND") throw new _aigne_afs.AFSNotFoundError(ctx.path);
1435
+ throw this.mapFrigateError(error, ctx.path);
1436
+ }
1437
+ }
1438
+ async unretainEvent(ctx) {
1439
+ const eventId = ctx.params.eventId;
1440
+ if (isPathTraversal(eventId)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
1441
+ try {
1442
+ await this.client.deleteRequest(`/api/events/${eventId}/retain`);
1443
+ this.client.invalidateCache("/api/events");
1444
+ return {
1445
+ success: true,
1446
+ data: { message: "Event unretained" }
1447
+ };
1448
+ } catch (error) {
1449
+ if (error instanceof FrigateApiError && error.code === "AFS_NOT_FOUND") throw new _aigne_afs.AFSNotFoundError(ctx.path);
1450
+ throw this.mapFrigateError(error, ctx.path);
1451
+ }
1452
+ }
1453
+ async deleteEvent(ctx) {
1454
+ const eventId = ctx.params.eventId;
1455
+ if (isPathTraversal(eventId)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
1456
+ try {
1457
+ await this.client.deleteRequest(`/api/events/${eventId}`);
1458
+ this.client.invalidateCache("/api/events");
1459
+ return {
1460
+ success: true,
1461
+ data: { message: "Event deleted" }
1462
+ };
1463
+ } catch (error) {
1464
+ if (error instanceof FrigateApiError && error.code === "AFS_NOT_FOUND") throw new _aigne_afs.AFSNotFoundError(ctx.path);
1465
+ throw this.mapFrigateError(error, ctx.path);
1466
+ }
1467
+ }
1468
+ async setEventSubLabel(ctx, args) {
1469
+ const eventId = ctx.params.eventId;
1470
+ if (isPathTraversal(eventId)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
1471
+ if (args.subLabel === void 0) throw new Error("subLabel is required");
1472
+ try {
1473
+ await this.client.postJSON(`/api/events/${eventId}/sub_label`, { subLabel: String(args.subLabel) });
1474
+ this.client.invalidateCache("/api/events");
1475
+ return {
1476
+ success: true,
1477
+ data: { message: "Sub-label updated" }
1478
+ };
1479
+ } catch (error) {
1480
+ if (error instanceof FrigateApiError && error.code === "AFS_NOT_FOUND") throw new _aigne_afs.AFSNotFoundError(ctx.path);
1481
+ throw this.mapFrigateError(error, ctx.path);
1482
+ }
1483
+ }
1484
+ async setEventDescription(ctx, args) {
1485
+ const eventId = ctx.params.eventId;
1486
+ if (isPathTraversal(eventId)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
1487
+ if (args.description === void 0) throw new Error("description is required");
1488
+ try {
1489
+ await this.client.putJSON(`/api/events/${eventId}/description`, { description: String(args.description) });
1490
+ this.client.invalidateCache("/api/events");
1491
+ return {
1492
+ success: true,
1493
+ data: { message: "Description updated" }
1494
+ };
1495
+ } catch (error) {
1496
+ if (error instanceof FrigateApiError && error.code === "AFS_NOT_FOUND") throw new _aigne_afs.AFSNotFoundError(ctx.path);
1497
+ throw this.mapFrigateError(error, ctx.path);
1498
+ }
1499
+ }
1500
+ async regenerateEventDescription(ctx) {
1501
+ const eventId = ctx.params.eventId;
1502
+ if (isPathTraversal(eventId)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
1503
+ try {
1504
+ const result = await this.client.putJSON(`/api/events/${eventId}/description/regenerate`, {});
1505
+ this.client.invalidateCache("/api/events");
1506
+ return {
1507
+ success: true,
1508
+ data: {
1509
+ message: "Description regenerated",
1510
+ description: result.description
1511
+ }
1512
+ };
1513
+ } catch (error) {
1514
+ if (error instanceof FrigateApiError && error.code === "AFS_NOT_FOUND") throw new _aigne_afs.AFSNotFoundError(ctx.path);
1515
+ throw this.mapFrigateError(error, ctx.path);
1516
+ }
1517
+ }
1518
+ async listCameraActions(ctx) {
1519
+ const cameraName = ctx.params.camera;
1520
+ if (isPathTraversal(cameraName)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
1521
+ const basePath = (0, ufo.joinURL)("/cameras", cameraName, ".actions");
1522
+ return { data: [
1523
+ this.buildActionEntry(basePath, "detect-on", "Enable object detection", void 0, "ambient"),
1524
+ this.buildActionEntry(basePath, "detect-off", "Disable object detection", void 0, "ambient"),
1525
+ this.buildActionEntry(basePath, "recordings-on", "Enable recording", void 0, "ambient"),
1526
+ this.buildActionEntry(basePath, "recordings-off", "Disable recording", void 0, "ambient"),
1527
+ this.buildActionEntry(basePath, "snapshots-on", "Enable snapshots", void 0, "ambient"),
1528
+ this.buildActionEntry(basePath, "snapshots-off", "Disable snapshots", void 0, "ambient"),
1529
+ this.buildActionEntry(basePath, "motion-on", "Enable motion detection", void 0, "ambient"),
1530
+ this.buildActionEntry(basePath, "motion-off", "Disable motion detection", void 0, "ambient"),
1531
+ this.buildActionEntry(basePath, "create-event", "Create manual event", {
1532
+ type: "object",
1533
+ properties: {
1534
+ label: { type: "string" },
1535
+ sub_label: { type: "string" },
1536
+ duration: { type: "number" },
1537
+ score: { type: "number" }
1538
+ }
1539
+ }, "boundary"),
1540
+ this.buildActionEntry(basePath, "export", "Export recording range", {
1541
+ type: "object",
1542
+ properties: {
1543
+ start_time: { type: "number" },
1544
+ end_time: { type: "number" },
1545
+ name: { type: "string" }
1546
+ },
1547
+ required: ["start_time", "end_time"]
1548
+ }, "boundary"),
1549
+ this.buildActionEntry(basePath, "ptz", "PTZ control", {
1550
+ type: "object",
1551
+ properties: {
1552
+ command: { type: "string" },
1553
+ preset: { type: "string" }
1554
+ }
1555
+ }, "boundary"),
1556
+ this.buildActionEntry(basePath, "notifications-on", "Enable notifications", void 0, "ambient"),
1557
+ this.buildActionEntry(basePath, "notifications-off", "Disable notifications", void 0, "ambient")
1558
+ ] };
1559
+ }
1560
+ async cameraDetectOn(ctx) {
1561
+ return this.toggleCameraFeature(ctx.params.camera, "detect", true, ctx.path);
1562
+ }
1563
+ async cameraDetectOff(ctx) {
1564
+ return this.toggleCameraFeature(ctx.params.camera, "detect", false, ctx.path);
1565
+ }
1566
+ async cameraRecordingsOn(ctx) {
1567
+ return this.toggleCameraFeature(ctx.params.camera, "recordings", true, ctx.path);
1568
+ }
1569
+ async cameraRecordingsOff(ctx) {
1570
+ return this.toggleCameraFeature(ctx.params.camera, "recordings", false, ctx.path);
1571
+ }
1572
+ async cameraSnapshotsOn(ctx) {
1573
+ return this.toggleCameraFeature(ctx.params.camera, "snapshots", true, ctx.path);
1574
+ }
1575
+ async cameraSnapshotsOff(ctx) {
1576
+ return this.toggleCameraFeature(ctx.params.camera, "snapshots", false, ctx.path);
1577
+ }
1578
+ async cameraMotionOn(ctx) {
1579
+ return this.toggleCameraFeature(ctx.params.camera, "motion", true, ctx.path);
1580
+ }
1581
+ async cameraMotionOff(ctx) {
1582
+ return this.toggleCameraFeature(ctx.params.camera, "motion", false, ctx.path);
1583
+ }
1584
+ async cameraCreateEvent(ctx, args) {
1585
+ const cameraName = ctx.params.camera;
1586
+ if (isPathTraversal(cameraName)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
1587
+ if (!(await this.client.getConfig()).cameras[cameraName]) throw new _aigne_afs.AFSNotFoundError(ctx.path);
1588
+ try {
1589
+ const result = await this.client.postJSON(`/api/${cameraName}/events/create`, {
1590
+ label: args.label,
1591
+ sub_label: args.sub_label,
1592
+ duration: args.duration,
1593
+ score: args.score
1594
+ });
1595
+ this.client.invalidateCache("/api/events");
1596
+ return {
1597
+ success: true,
1598
+ data: {
1599
+ message: "Manual event created",
1600
+ eventId: result.event_id
1601
+ }
1602
+ };
1603
+ } catch (error) {
1604
+ if (error instanceof FrigateApiError && error.code === "AFS_NOT_FOUND") throw new _aigne_afs.AFSNotFoundError(ctx.path);
1605
+ throw this.mapFrigateError(error, ctx.path);
1606
+ }
1607
+ }
1608
+ async cameraExport(ctx, args) {
1609
+ const cameraName = ctx.params.camera;
1610
+ if (isPathTraversal(cameraName)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
1611
+ if (!args.start_time || !args.end_time) throw new Error("start_time and end_time are required");
1612
+ if (!(await this.client.getConfig()).cameras[cameraName]) throw new _aigne_afs.AFSNotFoundError(ctx.path);
1613
+ try {
1614
+ return {
1615
+ success: true,
1616
+ data: {
1617
+ message: "Export started",
1618
+ exportId: (await this.client.postJSON(`/api/${cameraName}/export`, {
1619
+ start_time: args.start_time,
1620
+ end_time: args.end_time,
1621
+ name: args.name
1622
+ })).id
1623
+ }
1624
+ };
1625
+ } catch (error) {
1626
+ if (error instanceof FrigateApiError && error.code === "AFS_NOT_FOUND") throw new _aigne_afs.AFSNotFoundError(ctx.path);
1627
+ throw this.mapFrigateError(error, ctx.path);
1628
+ }
1629
+ }
1630
+ async cameraPtz(ctx, args) {
1631
+ const cameraName = ctx.params.camera;
1632
+ if (isPathTraversal(cameraName)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
1633
+ if (!(await this.client.getConfig()).cameras[cameraName]) throw new _aigne_afs.AFSNotFoundError(ctx.path);
1634
+ try {
1635
+ await this.client.postJSON(`/api/${cameraName}/ptz`, {
1636
+ command: args.command,
1637
+ preset: args.preset
1638
+ });
1639
+ return {
1640
+ success: true,
1641
+ data: { message: "PTZ command executed" }
1642
+ };
1643
+ } catch (error) {
1644
+ if (error instanceof FrigateApiError) {
1645
+ if (error.code === "AFS_NOT_FOUND") throw new _aigne_afs.AFSNotFoundError(ctx.path);
1646
+ if (error.statusCode === 400) throw new Error("Camera does not support PTZ");
1647
+ }
1648
+ throw this.mapFrigateError(error, ctx.path);
1649
+ }
1650
+ }
1651
+ async cameraNotificationsOn(ctx) {
1652
+ const cameraName = ctx.params.camera;
1653
+ if (isPathTraversal(cameraName)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
1654
+ if (!(await this.client.getConfig()).cameras[cameraName]) throw new _aigne_afs.AFSNotFoundError(ctx.path);
1655
+ try {
1656
+ await this.client.postText(`/api/${cameraName}/notifications/set`, "ON");
1657
+ return {
1658
+ success: true,
1659
+ data: { message: "Notifications enabled" }
1660
+ };
1661
+ } catch (error) {
1662
+ if (error instanceof FrigateApiError && error.code === "AFS_NOT_FOUND") throw new _aigne_afs.AFSNotFoundError(ctx.path);
1663
+ throw this.mapFrigateError(error, ctx.path);
1664
+ }
1665
+ }
1666
+ async cameraNotificationsOff(ctx) {
1667
+ const cameraName = ctx.params.camera;
1668
+ if (isPathTraversal(cameraName)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
1669
+ if (!(await this.client.getConfig()).cameras[cameraName]) throw new _aigne_afs.AFSNotFoundError(ctx.path);
1670
+ try {
1671
+ await this.client.postText(`/api/${cameraName}/notifications/set`, "OFF");
1672
+ return {
1673
+ success: true,
1674
+ data: { message: "Notifications disabled" }
1675
+ };
1676
+ } catch (error) {
1677
+ if (error instanceof FrigateApiError && error.code === "AFS_NOT_FOUND") throw new _aigne_afs.AFSNotFoundError(ctx.path);
1678
+ throw this.mapFrigateError(error, ctx.path);
1679
+ }
1680
+ }
1681
+ async listReviewActions(ctx) {
1682
+ const reviewId = ctx.params.reviewId;
1683
+ if (isPathTraversal(reviewId)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
1684
+ const basePath = (0, ufo.joinURL)("/reviews", reviewId, ".actions");
1685
+ return { data: [this.buildActionEntry(basePath, "mark-viewed", "Mark review as viewed", void 0, "ambient"), this.buildActionEntry(basePath, "unmark-viewed", "Unmark review as viewed", void 0, "ambient")] };
1686
+ }
1687
+ async markReviewViewed(ctx) {
1688
+ const reviewId = ctx.params.reviewId;
1689
+ if (isPathTraversal(reviewId)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
1690
+ try {
1691
+ await this.client.postJSON(`/api/review/${reviewId}/viewed`);
1692
+ this.client.invalidateCache("/api/review");
1693
+ return {
1694
+ success: true,
1695
+ data: { message: "Marked as viewed" }
1696
+ };
1697
+ } catch (error) {
1698
+ if (error instanceof FrigateApiError && error.code === "AFS_NOT_FOUND") throw new _aigne_afs.AFSNotFoundError(ctx.path);
1699
+ throw this.mapFrigateError(error, ctx.path);
1700
+ }
1701
+ }
1702
+ async unmarkReviewViewed(ctx) {
1703
+ const reviewId = ctx.params.reviewId;
1704
+ if (isPathTraversal(reviewId)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
1705
+ try {
1706
+ await this.client.deleteRequest(`/api/review/${reviewId}/viewed`);
1707
+ this.client.invalidateCache("/api/review");
1708
+ return {
1709
+ success: true,
1710
+ data: { message: "Unmarked as viewed" }
1711
+ };
1712
+ } catch (error) {
1713
+ if (error instanceof FrigateApiError && error.code === "AFS_NOT_FOUND") throw new _aigne_afs.AFSNotFoundError(ctx.path);
1714
+ throw this.mapFrigateError(error, ctx.path);
1715
+ }
1716
+ }
1717
+ async listExportActions(ctx) {
1718
+ const exportId = ctx.params.exportId;
1719
+ if (isPathTraversal(exportId)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
1720
+ const basePath = (0, ufo.joinURL)("/exports", exportId, ".actions");
1721
+ return { data: [this.buildActionEntry(basePath, "delete", "Delete export", void 0, "critical"), this.buildActionEntry(basePath, "rename", "Rename export", {
1722
+ type: "object",
1723
+ properties: { name: { type: "string" } },
1724
+ required: ["name"]
1725
+ }, "boundary")] };
1726
+ }
1727
+ async deleteExport(ctx) {
1728
+ const exportId = ctx.params.exportId;
1729
+ if (isPathTraversal(exportId)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
1730
+ try {
1731
+ await this.client.deleteRequest(`/api/exports/${exportId}`);
1732
+ return {
1733
+ success: true,
1734
+ data: { message: "Export deleted" }
1735
+ };
1736
+ } catch (error) {
1737
+ if (error instanceof FrigateApiError && error.code === "AFS_NOT_FOUND") throw new _aigne_afs.AFSNotFoundError(ctx.path);
1738
+ throw this.mapFrigateError(error, ctx.path);
1739
+ }
1740
+ }
1741
+ async renameExport(ctx, args) {
1742
+ const exportId = ctx.params.exportId;
1743
+ if (isPathTraversal(exportId)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
1744
+ if (!args.name) throw new Error("name is required");
1745
+ const name = String(args.name);
1746
+ if (name.includes("..") || name.includes("/") || name.includes("\\")) throw new Error("Invalid export name");
1747
+ try {
1748
+ await this.client.patchJSON(`/api/exports/${exportId}/rename`, { name });
1749
+ return {
1750
+ success: true,
1751
+ data: { message: "Export renamed" }
1752
+ };
1753
+ } catch (error) {
1754
+ if (error instanceof FrigateApiError && error.code === "AFS_NOT_FOUND") throw new _aigne_afs.AFSNotFoundError(ctx.path);
1755
+ throw this.mapFrigateError(error, ctx.path);
1756
+ }
1757
+ }
1758
+ async listReviewsActions(_ctx) {
1759
+ return { data: [this.buildActionEntry("/reviews/.actions", "summarize", "AI generate review summary", void 0, "ambient")] };
1760
+ }
1761
+ async summarizeReviews(_ctx) {
1762
+ try {
1763
+ return {
1764
+ success: true,
1765
+ data: {
1766
+ message: "Summary generated",
1767
+ summary: (await this.client.getReviewActivitySummary()).summary
1768
+ }
1769
+ };
1770
+ } catch (_error) {
1771
+ return {
1772
+ success: true,
1773
+ data: {
1774
+ message: "No reviews to summarize",
1775
+ summary: ""
1776
+ }
1777
+ };
1778
+ }
1779
+ }
1780
+ async listRootActions(_ctx) {
1781
+ return { data: [this.buildActionEntry("/.actions", "restart", "Restart Frigate", void 0, "critical"), this.buildActionEntry("/.actions", "create-user", "Create user", {
1782
+ type: "object",
1783
+ properties: {
1784
+ username: { type: "string" },
1785
+ password: { type: "string" },
1786
+ role: { type: "string" }
1787
+ },
1788
+ required: ["username", "password"]
1789
+ }, "boundary")] };
1790
+ }
1791
+ async restartFrigate(_ctx) {
1792
+ try {
1793
+ await this.client.postJSON("/api/restart");
1794
+ return {
1795
+ success: true,
1796
+ data: { message: "Frigate restart initiated" }
1797
+ };
1798
+ } catch (error) {
1799
+ throw this.mapFrigateError(error, "/");
1800
+ }
1801
+ }
1802
+ async createUser(_ctx, args) {
1803
+ if (!args.username || !args.password) throw new Error("username and password are required");
1804
+ try {
1805
+ await this.client.postJSON("/api/users", {
1806
+ username: String(args.username),
1807
+ password: String(args.password),
1808
+ role: args.role ? String(args.role) : "viewer"
1809
+ });
1810
+ return {
1811
+ success: true,
1812
+ data: { message: "User created" }
1813
+ };
1814
+ } catch (error) {
1815
+ throw this.mapFrigateError(error, "/");
1816
+ }
1817
+ }
1818
+ async listUserActions(ctx) {
1819
+ const basePath = (0, ufo.joinURL)("/users", ctx.params.username, ".actions");
1820
+ return { data: [this.buildActionEntry(basePath, "delete", "Delete user", void 0, "critical")] };
1821
+ }
1822
+ async deleteUser(ctx) {
1823
+ const username = ctx.params.username;
1824
+ if (isPathTraversal(username)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
1825
+ try {
1826
+ await this.client.deleteRequest(`/api/users/${username}`);
1827
+ return {
1828
+ success: true,
1829
+ data: { message: "User deleted" }
1830
+ };
1831
+ } catch (error) {
1832
+ if (error instanceof FrigateApiError && error.code === "AFS_NOT_FOUND") throw new _aigne_afs.AFSNotFoundError(ctx.path);
1833
+ throw this.mapFrigateError(error, ctx.path);
1834
+ }
1835
+ }
1836
+ async statHandler(ctx) {
1837
+ const path = (0, ufo.joinURL)("/", ctx.params.path ?? "");
1838
+ if (path === "/") return { data: {
1839
+ id: "/",
1840
+ path: "/",
1841
+ meta: {
1842
+ kind: "frigate:nvr",
1843
+ childrenCount: 8,
1844
+ description: "Frigate NVR instance"
1845
+ }
1846
+ } };
1847
+ const staticDirs = {
1848
+ "/cameras": {
1849
+ kind: "afs:directory",
1850
+ childrenCount: -1
1851
+ },
1852
+ "/events": {
1853
+ kind: "afs:directory",
1854
+ childrenCount: -1
1855
+ },
1856
+ "/reviews": {
1857
+ kind: "afs:directory",
1858
+ childrenCount: -1
1859
+ },
1860
+ "/exports": {
1861
+ kind: "afs:directory",
1862
+ childrenCount: -1
1863
+ },
1864
+ "/dashboard": {
1865
+ kind: "frigate:dashboard",
1866
+ childrenCount: 0
1867
+ },
1868
+ "/config": {
1869
+ kind: "frigate:config",
1870
+ childrenCount: 0
1871
+ },
1872
+ "/logs": {
1873
+ kind: "afs:directory",
1874
+ childrenCount: 3
1875
+ },
1876
+ "/users": {
1877
+ kind: "afs:directory",
1878
+ childrenCount: -1
1879
+ }
1880
+ };
1881
+ if (path === "/cameras") {
1882
+ const config = await this.client.getConfig();
1883
+ return { data: {
1884
+ id: "/cameras",
1885
+ path: "/cameras",
1886
+ meta: {
1887
+ kind: "afs:directory",
1888
+ childrenCount: Object.keys(config.cameras).length
1889
+ }
1890
+ } };
1891
+ }
1892
+ if (staticDirs[path]) return { data: {
1893
+ id: path,
1894
+ path,
1895
+ meta: staticDirs[path]
1896
+ } };
1897
+ const cameraMatch = path.match(/^\/cameras\/([^/]+)$/);
1898
+ if (cameraMatch) {
1899
+ const cameraName = cameraMatch[1];
1900
+ if (isPathTraversal(cameraName)) throw new _aigne_afs.AFSNotFoundError(path);
1901
+ if (!(await this.client.getConfig()).cameras[cameraName]) throw new _aigne_afs.AFSNotFoundError(path);
1902
+ return { data: {
1903
+ id: cameraName,
1904
+ path,
1905
+ meta: {
1906
+ kind: "frigate:camera",
1907
+ id: cameraName,
1908
+ childrenCount: 3,
1909
+ description: `${this.formatCameraName(cameraName)} camera`
1910
+ }
1911
+ } };
1912
+ }
1913
+ const cameraSubMatch = path.match(/^\/cameras\/([^/]+)\/(events|recordings|reviews)$/);
1914
+ if (cameraSubMatch) {
1915
+ const cameraName = cameraSubMatch[1];
1916
+ if (isPathTraversal(cameraName)) throw new _aigne_afs.AFSNotFoundError(path);
1917
+ if (!(await this.client.getConfig()).cameras[cameraName]) throw new _aigne_afs.AFSNotFoundError(path);
1918
+ return { data: {
1919
+ id: path,
1920
+ path,
1921
+ meta: {
1922
+ kind: "afs:directory",
1923
+ childrenCount: -1
1924
+ }
1925
+ } };
1926
+ }
1927
+ const cameraEventMatch = path.match(/^\/cameras\/([^/]+)\/events\/([^/]+)$/);
1928
+ if (cameraEventMatch) {
1929
+ const cameraName = cameraEventMatch[1];
1930
+ const eventId = cameraEventMatch[2];
1931
+ if (isPathTraversal(cameraName) || isPathTraversal(eventId)) throw new _aigne_afs.AFSNotFoundError(path);
1932
+ try {
1933
+ const event = await this.client.getEvent(eventId);
1934
+ if (event.camera !== cameraName) throw new _aigne_afs.AFSNotFoundError(path);
1935
+ return { data: {
1936
+ id: event.id,
1937
+ path,
1938
+ meta: {
1939
+ kind: "frigate:detection-event",
1940
+ childrenCount: 0,
1941
+ label: event.label,
1942
+ score: event.score,
1943
+ camera: event.camera
1944
+ }
1945
+ } };
1946
+ } catch (error) {
1947
+ if (error instanceof _aigne_afs.AFSNotFoundError) throw error;
1948
+ if (error instanceof FrigateApiError && error.code === "AFS_NOT_FOUND") throw new _aigne_afs.AFSNotFoundError(path);
1949
+ throw this.mapFrigateError(error, path);
1950
+ }
1951
+ }
1952
+ const eventMatch = path.match(/^\/events\/([^/]+)$/);
1953
+ if (eventMatch) {
1954
+ const eventId = eventMatch[1];
1955
+ if (eventId === "summary") return { data: {
1956
+ id: path,
1957
+ path,
1958
+ meta: {
1959
+ kind: "frigate:summary",
1960
+ childrenCount: 0
1961
+ }
1962
+ } };
1963
+ if (isPathTraversal(eventId)) throw new _aigne_afs.AFSNotFoundError(path);
1964
+ try {
1965
+ const event = await this.client.getEvent(eventId);
1966
+ return { data: {
1967
+ id: event.id,
1968
+ path,
1969
+ meta: {
1970
+ kind: "frigate:detection-event",
1971
+ childrenCount: 0,
1972
+ label: event.label,
1973
+ score: event.score,
1974
+ camera: event.camera
1975
+ }
1976
+ } };
1977
+ } catch (error) {
1978
+ if (error instanceof FrigateApiError && error.code === "AFS_NOT_FOUND") throw new _aigne_afs.AFSNotFoundError(path);
1979
+ throw this.mapFrigateError(error, path);
1980
+ }
1981
+ }
1982
+ const eventBinaryMatch = path.match(/^\/events\/([^/]+)\/(snapshot|thumbnail|clip)$/);
1983
+ if (eventBinaryMatch) {
1984
+ const eventId = eventBinaryMatch[1];
1985
+ const type = eventBinaryMatch[2];
1986
+ if (isPathTraversal(eventId)) throw new _aigne_afs.AFSNotFoundError(path);
1987
+ try {
1988
+ await this.client.getEvent(eventId);
1989
+ const kind = type === "clip" ? "frigate:video-clip" : "frigate:image";
1990
+ return { data: {
1991
+ id: `${eventId}/${type}`,
1992
+ path,
1993
+ meta: {
1994
+ kind,
1995
+ childrenCount: 0
1996
+ }
1997
+ } };
1998
+ } catch (error) {
1999
+ if (error instanceof FrigateApiError && error.code === "AFS_NOT_FOUND") throw new _aigne_afs.AFSNotFoundError(path);
2000
+ throw this.mapFrigateError(error, path);
2001
+ }
2002
+ }
2003
+ const reviewMatch = path.match(/^\/reviews\/([^/]+)$/);
2004
+ if (reviewMatch) {
2005
+ const reviewId = reviewMatch[1];
2006
+ if (reviewId === "summary") return { data: {
2007
+ id: path,
2008
+ path,
2009
+ meta: {
2010
+ kind: "frigate:summary",
2011
+ childrenCount: 0
2012
+ }
2013
+ } };
2014
+ if (isPathTraversal(reviewId)) throw new _aigne_afs.AFSNotFoundError(path);
2015
+ try {
2016
+ const review = await this.client.getReview(reviewId);
2017
+ return { data: {
2018
+ id: review.id,
2019
+ path,
2020
+ meta: {
2021
+ kind: "frigate:review",
2022
+ childrenCount: 0,
2023
+ camera: review.camera,
2024
+ severity: review.severity
2025
+ }
2026
+ } };
2027
+ } catch (error) {
2028
+ if (error instanceof FrigateApiError && error.code === "AFS_NOT_FOUND") throw new _aigne_afs.AFSNotFoundError(path);
2029
+ throw this.mapFrigateError(error, path);
2030
+ }
2031
+ }
2032
+ const exportMatch = path.match(/^\/exports\/([^/]+)$/);
2033
+ if (exportMatch) {
2034
+ const exportId = exportMatch[1];
2035
+ if (isPathTraversal(exportId)) throw new _aigne_afs.AFSNotFoundError(path);
2036
+ try {
2037
+ const exp = await this.client.getExport(exportId);
2038
+ return { data: {
2039
+ id: exp.id,
2040
+ path,
2041
+ meta: {
2042
+ kind: "frigate:export",
2043
+ childrenCount: 0,
2044
+ camera: exp.camera
2045
+ }
2046
+ } };
2047
+ } catch (error) {
2048
+ if (error instanceof FrigateApiError && error.code === "AFS_NOT_FOUND") throw new _aigne_afs.AFSNotFoundError(path);
2049
+ throw this.mapFrigateError(error, path);
2050
+ }
2051
+ }
2052
+ const logMatch = path.match(/^\/logs\/(frigate|nginx|go2rtc)$/);
2053
+ if (logMatch) return { data: {
2054
+ id: path,
2055
+ path,
2056
+ meta: {
2057
+ kind: "frigate:log",
2058
+ childrenCount: 0,
2059
+ service: logMatch[1]
2060
+ }
2061
+ } };
2062
+ if (path === "/config/raw") return { data: {
2063
+ id: path,
2064
+ path,
2065
+ meta: {
2066
+ kind: "frigate:config",
2067
+ childrenCount: 0
2068
+ }
2069
+ } };
2070
+ if (path === "/recordings/storage") return { data: {
2071
+ id: path,
2072
+ path,
2073
+ meta: {
2074
+ kind: "frigate:storage",
2075
+ childrenCount: 0
2076
+ }
2077
+ } };
2078
+ if (path === "/recordings") return { data: {
2079
+ id: path,
2080
+ path,
2081
+ meta: {
2082
+ kind: "afs:directory",
2083
+ childrenCount: -1
2084
+ }
2085
+ } };
2086
+ throw new _aigne_afs.AFSNotFoundError(path);
2087
+ }
2088
+ async explainRoot(_ctx) {
2089
+ const config = await this.client.getConfig();
2090
+ const stats = await this.client.getStats();
2091
+ const cameraNames = Object.keys(config.cameras);
2092
+ const lines = [];
2093
+ lines.push("Frigate NVR instance");
2094
+ lines.push(`Version: ${stats.service.version}`);
2095
+ lines.push(`Cameras: ${cameraNames.length} (${cameraNames.join(", ")})`);
2096
+ const detectorEntries = Object.entries(stats.detectors);
2097
+ for (const [name, det] of detectorEntries) lines.push(`Detector: ${name} (inference: ${det.inference_speed}ms)`);
2098
+ lines.push(`Uptime: ${this.formatUptime(stats.service.uptime)}`);
2099
+ return {
2100
+ content: lines.join("\n"),
2101
+ format: "text"
2102
+ };
2103
+ }
2104
+ async explainCamera(ctx) {
2105
+ const cameraName = ctx.params.camera;
2106
+ if (isPathTraversal(cameraName)) throw new _aigne_afs.AFSNotFoundError(ctx.path);
2107
+ const cam = (await this.client.getConfig()).cameras[cameraName];
2108
+ if (!cam) throw new _aigne_afs.AFSNotFoundError(ctx.path);
2109
+ const lines = [];
2110
+ lines.push(`Camera: ${cameraName}`);
2111
+ if (cam.detect) {
2112
+ lines.push(`Resolution: ${cam.detect.width}x${cam.detect.height} @ ${cam.detect.fps} FPS`);
2113
+ lines.push(`Detection: ${cam.detect.enabled ? "enabled" : "disabled"}`);
2114
+ }
2115
+ if (cam.record) lines.push(`Recording: ${cam.record.enabled ? "enabled" : "disabled"}`);
2116
+ if (cam.zones && Object.keys(cam.zones).length > 0) lines.push(`Zones: ${Object.keys(cam.zones).join(", ")}`);
2117
+ return {
2118
+ content: lines.join("\n"),
2119
+ format: "text"
2120
+ };
2121
+ }
2122
+ async explainGeneric(ctx) {
2123
+ const path = (0, ufo.joinURL)("/", ctx.params.path ?? "");
2124
+ try {
2125
+ await this.statHandler(ctx);
2126
+ } catch {
2127
+ throw new _aigne_afs.AFSNotFoundError(path);
2128
+ }
2129
+ return {
2130
+ content: `Frigate NVR path: ${path}`,
2131
+ format: "text"
2132
+ };
2133
+ }
2134
+ eventsToEntries(events, basePath) {
2135
+ return events.map((event) => this.eventToEntry(event, basePath));
2136
+ }
2137
+ eventToEntry(event, basePath) {
2138
+ const content = event.description || `${event.label} detected on ${event.camera}`;
2139
+ return this.buildEntry((0, ufo.joinURL)(basePath, event.id), {
2140
+ id: event.id,
2141
+ content,
2142
+ meta: {
2143
+ kind: "frigate:detection-event",
2144
+ id: event.id,
2145
+ childrenCount: 0,
2146
+ label: event.label,
2147
+ score: event.score,
2148
+ camera: event.camera,
2149
+ topScore: event.top_score,
2150
+ zones: event.zones,
2151
+ startTime: event.start_time,
2152
+ endTime: event.end_time,
2153
+ hasClip: event.has_clip,
2154
+ hasSnapshot: event.has_snapshot
2155
+ }
2156
+ });
2157
+ }
2158
+ reviewsToEntries(reviews) {
2159
+ return reviews.map((review) => this.reviewToEntry(review));
2160
+ }
2161
+ reviewToEntry(review) {
2162
+ return this.buildEntry((0, ufo.joinURL)("/reviews", review.id), {
2163
+ id: review.id,
2164
+ content: `Review: ${review.severity} on ${review.camera} (${review.data.objects.join(", ")})`,
2165
+ meta: {
2166
+ kind: "frigate:review",
2167
+ id: review.id,
2168
+ childrenCount: 0,
2169
+ camera: review.camera,
2170
+ severity: review.severity,
2171
+ labels: review.data.objects,
2172
+ zones: review.data.zones,
2173
+ startTime: review.start_time,
2174
+ endTime: review.end_time,
2175
+ hasBeenReviewed: review.has_been_reviewed
2176
+ }
2177
+ });
2178
+ }
2179
+ buildActionEntry(basePath, name, description, inputSchema, severity) {
2180
+ return {
2181
+ id: name,
2182
+ path: (0, ufo.joinURL)(basePath, name),
2183
+ summary: name,
2184
+ meta: {
2185
+ kind: "afs:executable",
2186
+ kinds: ["afs:executable", "afs:node"],
2187
+ name,
2188
+ description,
2189
+ ...severity ? { severity } : {},
2190
+ ...inputSchema ? { inputSchema } : {}
2191
+ }
2192
+ };
2193
+ }
2194
+ async toggleCameraFeature(cameraName, feature, enabled, path) {
2195
+ if (isPathTraversal(cameraName)) throw new _aigne_afs.AFSNotFoundError(path);
2196
+ if (!(await this.client.getConfig()).cameras[cameraName]) throw new _aigne_afs.AFSNotFoundError(path);
2197
+ try {
2198
+ await this.client.postText(`/api/${cameraName}/${feature}/set`, enabled ? "ON" : "OFF");
2199
+ this.client.invalidateCache("/api/config");
2200
+ return {
2201
+ success: true,
2202
+ data: { message: `${feature} ${enabled ? "enabled" : "disabled"} for ${cameraName}` }
2203
+ };
2204
+ } catch (error) {
2205
+ if (error instanceof FrigateApiError && error.code === "AFS_NOT_FOUND") throw new _aigne_afs.AFSNotFoundError(path);
2206
+ throw this.mapFrigateError(error, path);
2207
+ }
2208
+ }
2209
+ arrayBufferToBase64(buffer) {
2210
+ const bytes = new Uint8Array(buffer);
2211
+ let binary = "";
2212
+ for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
2213
+ return btoa(binary);
2214
+ }
2215
+ formatCameraName(name) {
2216
+ return name.replace(/[_-]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()).trim();
2217
+ }
2218
+ formatUptime(seconds) {
2219
+ const days = Math.floor(seconds / 86400);
2220
+ const hours = Math.floor(seconds % 86400 / 3600);
2221
+ if (days > 0) return `${days} days${hours > 0 ? `, ${hours} hours` : ""}`;
2222
+ if (hours > 0) return `${hours} hours`;
2223
+ return `${Math.floor(seconds / 60)} minutes`;
2224
+ }
2225
+ mapFrigateError(error, path) {
2226
+ if (error instanceof FrigateApiError) {
2227
+ if (error.code === "AFS_NOT_FOUND") return new _aigne_afs.AFSNotFoundError(path);
2228
+ const afsError = new Error(error.message);
2229
+ afsError.code = error.code;
2230
+ return afsError;
2231
+ }
2232
+ return error instanceof Error ? error : /* @__PURE__ */ new Error("Unknown error");
2233
+ }
2234
+ };
2235
+ require_decorate.__decorate([(0, _aigne_afs_provider.Meta)("/cameras/:camera/events")], AFSFrigate.prototype, "readCameraEventsMeta", null);
2236
+ require_decorate.__decorate([(0, _aigne_afs_provider.Meta)("/cameras/:camera")], AFSFrigate.prototype, "readCameraMeta", null);
2237
+ require_decorate.__decorate([(0, _aigne_afs_provider.Meta)("/:path*")], AFSFrigate.prototype, "readMetaHandler", null);
2238
+ require_decorate.__decorate([(0, _aigne_afs_provider.Read)("/.meta/.capabilities")], AFSFrigate.prototype, "readCapabilities", null);
2239
+ require_decorate.__decorate([(0, _aigne_afs_provider.List)("/")], AFSFrigate.prototype, "listRoot", null);
2240
+ require_decorate.__decorate([(0, _aigne_afs_provider.Read)("/")], AFSFrigate.prototype, "readRoot", null);
2241
+ require_decorate.__decorate([(0, _aigne_afs_provider.List)("/cameras")], AFSFrigate.prototype, "listCameras", null);
2242
+ require_decorate.__decorate([(0, _aigne_afs_provider.Read)("/cameras")], AFSFrigate.prototype, "readCamerasDir", null);
2243
+ require_decorate.__decorate([(0, _aigne_afs_provider.List)("/cameras/:camera")], AFSFrigate.prototype, "listCamera", null);
2244
+ require_decorate.__decorate([(0, _aigne_afs_provider.Read)("/cameras/:camera")], AFSFrigate.prototype, "readCamera", null);
2245
+ require_decorate.__decorate([(0, _aigne_afs_provider.List)("/cameras/:camera/events")], AFSFrigate.prototype, "listCameraEvents", null);
2246
+ require_decorate.__decorate([(0, _aigne_afs_provider.Read)("/cameras/:camera/events")], AFSFrigate.prototype, "readCameraEventsDir", null);
2247
+ require_decorate.__decorate([(0, _aigne_afs_provider.Read)("/cameras/:camera/events/:eventId")], AFSFrigate.prototype, "readCameraEvent", null);
2248
+ require_decorate.__decorate([(0, _aigne_afs_provider.Read)("/cameras/:camera/latest")], AFSFrigate.prototype, "readCameraLatest", null);
2249
+ require_decorate.__decorate([(0, _aigne_afs_provider.Read)("/cameras/:camera/grid")], AFSFrigate.prototype, "readCameraGrid", null);
2250
+ require_decorate.__decorate([(0, _aigne_afs_provider.Read)("/cameras/:camera/:label/best")], AFSFrigate.prototype, "readCameraBestSnapshot", null);
2251
+ require_decorate.__decorate([(0, _aigne_afs_provider.Read)("/cameras/:camera/ptz")], AFSFrigate.prototype, "readCameraPtz", null);
2252
+ require_decorate.__decorate([(0, _aigne_afs_provider.List)("/cameras/:camera/recordings")], AFSFrigate.prototype, "listCameraRecordings", null);
2253
+ require_decorate.__decorate([(0, _aigne_afs_provider.Read)("/cameras/:camera/recordings")], AFSFrigate.prototype, "readCameraRecordingsDir", null);
2254
+ require_decorate.__decorate([(0, _aigne_afs_provider.Read)("/cameras/:camera/recordings/summary")], AFSFrigate.prototype, "readCameraRecordingsSummary", null);
2255
+ require_decorate.__decorate([(0, _aigne_afs_provider.Read)("/cameras/:camera/recordings/:time/snapshot")], AFSFrigate.prototype, "readRecordingSnapshot", null);
2256
+ require_decorate.__decorate([(0, _aigne_afs_provider.List)("/cameras/:camera/reviews")], AFSFrigate.prototype, "listCameraReviews", null);
2257
+ require_decorate.__decorate([(0, _aigne_afs_provider.Read)("/cameras/:camera/reviews")], AFSFrigate.prototype, "readCameraReviewsDir", null);
2258
+ require_decorate.__decorate([(0, _aigne_afs_provider.List)("/events")], AFSFrigate.prototype, "listEvents", null);
2259
+ require_decorate.__decorate([(0, _aigne_afs_provider.Read)("/events")], AFSFrigate.prototype, "readEventsDir", null);
2260
+ require_decorate.__decorate([(0, _aigne_afs_provider.Read)("/events/summary")], AFSFrigate.prototype, "readEventsSummary", null);
2261
+ require_decorate.__decorate([(0, _aigne_afs_provider.Read)("/events/:eventId")], AFSFrigate.prototype, "readEvent", null);
2262
+ require_decorate.__decorate([(0, _aigne_afs_provider.Read)("/events/:eventId/snapshot")], AFSFrigate.prototype, "readEventSnapshot", null);
2263
+ require_decorate.__decorate([(0, _aigne_afs_provider.Read)("/events/:eventId/thumbnail")], AFSFrigate.prototype, "readEventThumbnail", null);
2264
+ require_decorate.__decorate([(0, _aigne_afs_provider.Read)("/events/:eventId/clip")], AFSFrigate.prototype, "readEventClip", null);
2265
+ require_decorate.__decorate([(0, _aigne_afs_provider.List)("/reviews")], AFSFrigate.prototype, "listReviews", null);
2266
+ require_decorate.__decorate([(0, _aigne_afs_provider.Read)("/reviews")], AFSFrigate.prototype, "readReviewsDir", null);
2267
+ require_decorate.__decorate([(0, _aigne_afs_provider.Read)("/reviews/summary")], AFSFrigate.prototype, "readReviewsSummary", null);
2268
+ require_decorate.__decorate([(0, _aigne_afs_provider.Read)("/reviews/:reviewId")], AFSFrigate.prototype, "readReview", null);
2269
+ require_decorate.__decorate([(0, _aigne_afs_provider.List)("/exports")], AFSFrigate.prototype, "listExports", null);
2270
+ require_decorate.__decorate([(0, _aigne_afs_provider.Read)("/exports")], AFSFrigate.prototype, "readExportsDir", null);
2271
+ require_decorate.__decorate([(0, _aigne_afs_provider.Read)("/exports/:exportId")], AFSFrigate.prototype, "readExport", null);
2272
+ require_decorate.__decorate([(0, _aigne_afs_provider.Read)("/dashboard")], AFSFrigate.prototype, "readDashboard", null);
2273
+ require_decorate.__decorate([(0, _aigne_afs_provider.Read)("/config")], AFSFrigate.prototype, "readConfig", null);
2274
+ require_decorate.__decorate([(0, _aigne_afs_provider.Read)("/config/raw")], AFSFrigate.prototype, "readConfigRaw", null);
2275
+ require_decorate.__decorate([(0, _aigne_afs_provider.Write)("/config")], AFSFrigate.prototype, "writeConfig", null);
2276
+ require_decorate.__decorate([(0, _aigne_afs_provider.Read)("/logs")], AFSFrigate.prototype, "readLogsDir", null);
2277
+ require_decorate.__decorate([(0, _aigne_afs_provider.List)("/logs")], AFSFrigate.prototype, "listLogs", null);
2278
+ require_decorate.__decorate([(0, _aigne_afs_provider.Read)("/logs/:service")], AFSFrigate.prototype, "readLog", null);
2279
+ require_decorate.__decorate([(0, _aigne_afs_provider.List)("/users")], AFSFrigate.prototype, "listUsers", null);
2280
+ require_decorate.__decorate([(0, _aigne_afs_provider.Read)("/users")], AFSFrigate.prototype, "readUsersDir", null);
2281
+ require_decorate.__decorate([(0, _aigne_afs_provider.Read)("/users/:username")], AFSFrigate.prototype, "readUser", null);
2282
+ require_decorate.__decorate([(0, _aigne_afs_provider.Read)("/recordings/storage")], AFSFrigate.prototype, "readRecordingsStorage", null);
2283
+ require_decorate.__decorate([(0, _aigne_afs_provider.List)("/events/:eventId")], AFSFrigate.prototype, "listEventLeaf", null);
2284
+ require_decorate.__decorate([(0, _aigne_afs_provider.List)("/cameras/:camera/events/:eventId")], AFSFrigate.prototype, "listCameraEventLeaf", null);
2285
+ require_decorate.__decorate([(0, _aigne_afs_provider.List)("/dashboard")], AFSFrigate.prototype, "listDashboard", null);
2286
+ require_decorate.__decorate([(0, _aigne_afs_provider.List)("/config")], AFSFrigate.prototype, "listConfig", null);
2287
+ require_decorate.__decorate([(0, _aigne_afs_provider.List)("/reviews/:reviewId")], AFSFrigate.prototype, "listReviewLeaf", null);
2288
+ require_decorate.__decorate([(0, _aigne_afs_provider.List)("/exports/:exportId")], AFSFrigate.prototype, "listExportLeaf", null);
2289
+ require_decorate.__decorate([(0, _aigne_afs_provider.Search)("/events")], AFSFrigate.prototype, "searchEvents", null);
2290
+ require_decorate.__decorate([(0, _aigne_afs_provider.Search)("/:path*")], AFSFrigate.prototype, "searchGeneric", null);
2291
+ require_decorate.__decorate([(0, _aigne_afs_provider.Actions)("/events/:eventId")], AFSFrigate.prototype, "listEventActions", null);
2292
+ require_decorate.__decorate([_aigne_afs_provider.Actions.Exec("/events/:eventId", "retain")], AFSFrigate.prototype, "retainEvent", null);
2293
+ require_decorate.__decorate([_aigne_afs_provider.Actions.Exec("/events/:eventId", "unretain")], AFSFrigate.prototype, "unretainEvent", null);
2294
+ require_decorate.__decorate([_aigne_afs_provider.Actions.Exec("/events/:eventId", "delete")], AFSFrigate.prototype, "deleteEvent", null);
2295
+ require_decorate.__decorate([_aigne_afs_provider.Actions.Exec("/events/:eventId", "set-sub-label")], AFSFrigate.prototype, "setEventSubLabel", null);
2296
+ require_decorate.__decorate([_aigne_afs_provider.Actions.Exec("/events/:eventId", "set-description")], AFSFrigate.prototype, "setEventDescription", null);
2297
+ require_decorate.__decorate([_aigne_afs_provider.Actions.Exec("/events/:eventId", "regenerate-description")], AFSFrigate.prototype, "regenerateEventDescription", null);
2298
+ require_decorate.__decorate([(0, _aigne_afs_provider.Actions)("/cameras/:camera")], AFSFrigate.prototype, "listCameraActions", null);
2299
+ require_decorate.__decorate([_aigne_afs_provider.Actions.Exec("/cameras/:camera", "detect-on")], AFSFrigate.prototype, "cameraDetectOn", null);
2300
+ require_decorate.__decorate([_aigne_afs_provider.Actions.Exec("/cameras/:camera", "detect-off")], AFSFrigate.prototype, "cameraDetectOff", null);
2301
+ require_decorate.__decorate([_aigne_afs_provider.Actions.Exec("/cameras/:camera", "recordings-on")], AFSFrigate.prototype, "cameraRecordingsOn", null);
2302
+ require_decorate.__decorate([_aigne_afs_provider.Actions.Exec("/cameras/:camera", "recordings-off")], AFSFrigate.prototype, "cameraRecordingsOff", null);
2303
+ require_decorate.__decorate([_aigne_afs_provider.Actions.Exec("/cameras/:camera", "snapshots-on")], AFSFrigate.prototype, "cameraSnapshotsOn", null);
2304
+ require_decorate.__decorate([_aigne_afs_provider.Actions.Exec("/cameras/:camera", "snapshots-off")], AFSFrigate.prototype, "cameraSnapshotsOff", null);
2305
+ require_decorate.__decorate([_aigne_afs_provider.Actions.Exec("/cameras/:camera", "motion-on")], AFSFrigate.prototype, "cameraMotionOn", null);
2306
+ require_decorate.__decorate([_aigne_afs_provider.Actions.Exec("/cameras/:camera", "motion-off")], AFSFrigate.prototype, "cameraMotionOff", null);
2307
+ require_decorate.__decorate([_aigne_afs_provider.Actions.Exec("/cameras/:camera", "create-event")], AFSFrigate.prototype, "cameraCreateEvent", null);
2308
+ require_decorate.__decorate([_aigne_afs_provider.Actions.Exec("/cameras/:camera", "export")], AFSFrigate.prototype, "cameraExport", null);
2309
+ require_decorate.__decorate([_aigne_afs_provider.Actions.Exec("/cameras/:camera", "ptz")], AFSFrigate.prototype, "cameraPtz", null);
2310
+ require_decorate.__decorate([_aigne_afs_provider.Actions.Exec("/cameras/:camera", "notifications-on")], AFSFrigate.prototype, "cameraNotificationsOn", null);
2311
+ require_decorate.__decorate([_aigne_afs_provider.Actions.Exec("/cameras/:camera", "notifications-off")], AFSFrigate.prototype, "cameraNotificationsOff", null);
2312
+ require_decorate.__decorate([(0, _aigne_afs_provider.Actions)("/reviews/:reviewId")], AFSFrigate.prototype, "listReviewActions", null);
2313
+ require_decorate.__decorate([_aigne_afs_provider.Actions.Exec("/reviews/:reviewId", "mark-viewed")], AFSFrigate.prototype, "markReviewViewed", null);
2314
+ require_decorate.__decorate([_aigne_afs_provider.Actions.Exec("/reviews/:reviewId", "unmark-viewed")], AFSFrigate.prototype, "unmarkReviewViewed", null);
2315
+ require_decorate.__decorate([(0, _aigne_afs_provider.Actions)("/exports/:exportId")], AFSFrigate.prototype, "listExportActions", null);
2316
+ require_decorate.__decorate([_aigne_afs_provider.Actions.Exec("/exports/:exportId", "delete")], AFSFrigate.prototype, "deleteExport", null);
2317
+ require_decorate.__decorate([_aigne_afs_provider.Actions.Exec("/exports/:exportId", "rename")], AFSFrigate.prototype, "renameExport", null);
2318
+ require_decorate.__decorate([(0, _aigne_afs_provider.Actions)("/reviews")], AFSFrigate.prototype, "listReviewsActions", null);
2319
+ require_decorate.__decorate([_aigne_afs_provider.Actions.Exec("/reviews", "summarize")], AFSFrigate.prototype, "summarizeReviews", null);
2320
+ require_decorate.__decorate([(0, _aigne_afs_provider.Actions)("/")], AFSFrigate.prototype, "listRootActions", null);
2321
+ require_decorate.__decorate([_aigne_afs_provider.Actions.Exec("/", "restart")], AFSFrigate.prototype, "restartFrigate", null);
2322
+ require_decorate.__decorate([_aigne_afs_provider.Actions.Exec("/", "create-user")], AFSFrigate.prototype, "createUser", null);
2323
+ require_decorate.__decorate([(0, _aigne_afs_provider.Actions)("/users/:username")], AFSFrigate.prototype, "listUserActions", null);
2324
+ require_decorate.__decorate([_aigne_afs_provider.Actions.Exec("/users/:username", "delete")], AFSFrigate.prototype, "deleteUser", null);
2325
+ require_decorate.__decorate([(0, _aigne_afs_provider.Stat)("/:path*")], AFSFrigate.prototype, "statHandler", null);
2326
+ require_decorate.__decorate([(0, _aigne_afs_provider.Explain)("/")], AFSFrigate.prototype, "explainRoot", null);
2327
+ require_decorate.__decorate([(0, _aigne_afs_provider.Explain)("/cameras/:camera")], AFSFrigate.prototype, "explainCamera", null);
2328
+ require_decorate.__decorate([(0, _aigne_afs_provider.Explain)("/:path*")], AFSFrigate.prototype, "explainGeneric", null);
2329
+ var src_default = AFSFrigate;
2330
+
2331
+ //#endregion
2332
+ exports.AFSFrigate = AFSFrigate;
2333
+ exports.default = src_default;