@camstack/addon-ai 0.1.3 → 0.1.5
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/addon.js +203 -14
- package/dist/addon.mjs +203 -14
- package/package.json +1 -1
package/dist/addon.js
CHANGED
|
@@ -43,7 +43,7 @@ let node_fs_promises = require("node:fs/promises");
|
|
|
43
43
|
node_fs_promises = __toESM(node_fs_promises);
|
|
44
44
|
let node_child_process = require("node:child_process");
|
|
45
45
|
let node_net = require("node:net");
|
|
46
|
-
//#region ../types/dist/event-category-
|
|
46
|
+
//#region ../types/dist/event-category-H4AVePnn.mjs
|
|
47
47
|
var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
48
48
|
EventCategory["SystemBoot"] = "system.boot";
|
|
49
49
|
EventCategory["SystemAddonsReady"] = "system.addons-ready";
|
|
@@ -193,6 +193,9 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
193
193
|
EventCategory["RecordingSegmentWritten"] = "recording.segment.written";
|
|
194
194
|
EventCategory["RecordingPolicyFallback"] = "recording.policy.fallback";
|
|
195
195
|
EventCategory["RecordingRetentionCompleted"] = "recording.retention.completed";
|
|
196
|
+
/** Runner-sampled scrub thumbnail (~1/5 s/camera). Telemetry (D8): a lost
|
|
197
|
+
* thumb is a scrub gap the recorder's keyframe backfill covers. */
|
|
198
|
+
EventCategory["RecordingThumbSampled"] = "recording.thumb-sampled";
|
|
196
199
|
EventCategory["DetectionEvent"] = "detection.event";
|
|
197
200
|
EventCategory["SessionTrackNew"] = "session.track.new";
|
|
198
201
|
EventCategory["SessionTrackExpired"] = "session.track.expired";
|
|
@@ -7262,6 +7265,24 @@ var RecordingRetentionSchema = object({
|
|
|
7262
7265
|
maxSizeGb: number().min(0).optional()
|
|
7263
7266
|
});
|
|
7264
7267
|
/**
|
|
7268
|
+
* Scrub-thumbnail fidelity preset — the single per-camera selector bundling the
|
|
7269
|
+
* sprite tile RESOLUTION + JPEG QUALITY the recorder packs timeline-scrub
|
|
7270
|
+
* previews at. Five graduated steps; absent on a config = `standard` (the
|
|
7271
|
+
* shipped default, matching `sheet-geometry`/`sheet-composer`).
|
|
7272
|
+
*
|
|
7273
|
+
* Existing sheets are IMMUTABLE — a changed preset applies to NEW windows only.
|
|
7274
|
+
* Each window's index sidecar carries its own tile dims, so a camera whose
|
|
7275
|
+
* preset changed over time renders every historical window at the dims it was
|
|
7276
|
+
* written with.
|
|
7277
|
+
*/
|
|
7278
|
+
var ScrubThumbnailPresetSchema = _enum([
|
|
7279
|
+
"minimal",
|
|
7280
|
+
"low",
|
|
7281
|
+
"standard",
|
|
7282
|
+
"high",
|
|
7283
|
+
"max"
|
|
7284
|
+
]);
|
|
7285
|
+
/**
|
|
7265
7286
|
* The full per-camera recording intent — the wire shape of a RecordingTarget.
|
|
7266
7287
|
*
|
|
7267
7288
|
* `mode` is the authoritative storage choice; `schedule`/`triggers`/`pre`/`post`
|
|
@@ -7298,7 +7319,14 @@ var RecordingConfigSchema = object({
|
|
|
7298
7319
|
* derived into bands once via `migrateConfigToBands`.
|
|
7299
7320
|
*/
|
|
7300
7321
|
bands: array(RecordingBandSchema).optional(),
|
|
7301
|
-
retention: RecordingRetentionSchema.optional()
|
|
7322
|
+
retention: RecordingRetentionSchema.optional(),
|
|
7323
|
+
/**
|
|
7324
|
+
* Per-camera scrub-thumbnail fidelity preset (resolution + JPEG quality for
|
|
7325
|
+
* timeline-scrub sprite previews). Absent = `standard`. Applies to NEW
|
|
7326
|
+
* windows only — existing sheets are immutable, and each window's index
|
|
7327
|
+
* carries its own tile dims so mixed-preset history renders correctly.
|
|
7328
|
+
*/
|
|
7329
|
+
scrubThumbnails: ScrubThumbnailPresetSchema.optional()
|
|
7302
7330
|
});
|
|
7303
7331
|
/**
|
|
7304
7332
|
* `StorageLocationType` — an addon-declared id that identifies the *kind* of
|
|
@@ -9887,7 +9915,7 @@ object({
|
|
|
9887
9915
|
deviceId: number(),
|
|
9888
9916
|
timestamp: number()
|
|
9889
9917
|
});
|
|
9890
|
-
DeviceType.Button;
|
|
9918
|
+
DeviceType.Button, DeviceType.Camera;
|
|
9891
9919
|
/**
|
|
9892
9920
|
* Enum-state sensor — a string value picked from a finite option set.
|
|
9893
9921
|
* Drives HA `sensor` entries with `state_class: enum` (HVAC action
|
|
@@ -14257,6 +14285,14 @@ var ConfigEntrySchema = object({
|
|
|
14257
14285
|
value: unknown(),
|
|
14258
14286
|
description: string().optional()
|
|
14259
14287
|
});
|
|
14288
|
+
var DeviceLinkModeSchema = _enum(["auto", "manual"]);
|
|
14289
|
+
/** One resolved linked device — the compact projection consumers need. */
|
|
14290
|
+
var LinkedDeviceSchema = object({
|
|
14291
|
+
deviceId: number(),
|
|
14292
|
+
name: string(),
|
|
14293
|
+
location: string().nullable(),
|
|
14294
|
+
features: array(string())
|
|
14295
|
+
});
|
|
14260
14296
|
var SavedDeviceRowSchema = object({
|
|
14261
14297
|
/** Numeric id reserved at allocateDeviceId time. */
|
|
14262
14298
|
id: number(),
|
|
@@ -14478,7 +14514,10 @@ method(object({
|
|
|
14478
14514
|
}), _void(), {
|
|
14479
14515
|
kind: "mutation",
|
|
14480
14516
|
auth: "admin"
|
|
14481
|
-
}), method(object({ addonId: string() }), array(SavedDeviceRowSchema)), method(object({ addonId: string().optional() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }),
|
|
14517
|
+
}), method(object({ addonId: string() }), array(SavedDeviceRowSchema)), method(object({ addonId: string().optional() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
|
|
14518
|
+
mode: DeviceLinkModeSchema,
|
|
14519
|
+
devices: array(LinkedDeviceSchema)
|
|
14520
|
+
})), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
|
|
14482
14521
|
deviceId: number(),
|
|
14483
14522
|
values: record(string(), unknown())
|
|
14484
14523
|
}), object({ success: literal(true) }), {
|
|
@@ -15792,6 +15831,61 @@ var EventKindSchema = _enum([
|
|
|
15792
15831
|
"object",
|
|
15793
15832
|
"audio"
|
|
15794
15833
|
]);
|
|
15834
|
+
/**
|
|
15835
|
+
* Spatial filter for `listTracks` — the rect + polygon variants of the shared
|
|
15836
|
+
* MaskShape vocabulary (see `mask-shape.ts`). Coordinates are NORMALIZED 0..1
|
|
15837
|
+
* of the camera frame (top-left origin), matching the drawing-plane editor.
|
|
15838
|
+
*/
|
|
15839
|
+
var TrackZoneFilterSchema = discriminatedUnion("kind", [MaskRectShapeSchema, MaskPolygonShapeSchema]);
|
|
15840
|
+
/** Closed icon vocabulary so clients render a known glyph per kind. */
|
|
15841
|
+
var EventKindIconSchema = _enum([
|
|
15842
|
+
"motion",
|
|
15843
|
+
"audio",
|
|
15844
|
+
"person",
|
|
15845
|
+
"vehicle",
|
|
15846
|
+
"animal",
|
|
15847
|
+
"door",
|
|
15848
|
+
"pir",
|
|
15849
|
+
"smoke",
|
|
15850
|
+
"water",
|
|
15851
|
+
"button",
|
|
15852
|
+
"generic"
|
|
15853
|
+
]);
|
|
15854
|
+
var EventKindCategorySchema = _enum([
|
|
15855
|
+
"motion",
|
|
15856
|
+
"audio",
|
|
15857
|
+
"detection",
|
|
15858
|
+
"sensor",
|
|
15859
|
+
"custom"
|
|
15860
|
+
]);
|
|
15861
|
+
var EventKindDescriptorSchema = object({
|
|
15862
|
+
/** Stable kind id (e.g. 'motion', 'person', 'contact'). */
|
|
15863
|
+
kind: string(),
|
|
15864
|
+
label: string(),
|
|
15865
|
+
/** Hex color for timeline/legend rendering. */
|
|
15866
|
+
color: string(),
|
|
15867
|
+
icon: EventKindIconSchema,
|
|
15868
|
+
category: EventKindCategorySchema,
|
|
15869
|
+
/** Which cap + device contributes this kind. For built-ins the camera
|
|
15870
|
+
* itself; for sensor kinds the LINKED source device. */
|
|
15871
|
+
source: object({
|
|
15872
|
+
capName: string(),
|
|
15873
|
+
deviceId: number()
|
|
15874
|
+
})
|
|
15875
|
+
});
|
|
15876
|
+
var SensorEventSchema = object({
|
|
15877
|
+
id: string(),
|
|
15878
|
+
/** The CAMERA the event is attributed to (a sensor linked to N cameras
|
|
15879
|
+
* yields N rows, one per camera). */
|
|
15880
|
+
deviceId: number(),
|
|
15881
|
+
/** The linked sensor device whose state changed. */
|
|
15882
|
+
sourceDeviceId: number(),
|
|
15883
|
+
/** Event kind id — matches an `EventKindDescriptor.kind`. */
|
|
15884
|
+
kind: string(),
|
|
15885
|
+
/** Snapshot of the sensor cap's runtime-state slice at the change. */
|
|
15886
|
+
value: record(string(), unknown()).nullable(),
|
|
15887
|
+
timestamp: number()
|
|
15888
|
+
});
|
|
15795
15889
|
var TrackPositionSchema = object({
|
|
15796
15890
|
x: number(),
|
|
15797
15891
|
y: number(),
|
|
@@ -15805,6 +15899,30 @@ var TrackSnapshotSchema = object({
|
|
|
15805
15899
|
mediaKey: string()
|
|
15806
15900
|
});
|
|
15807
15901
|
/**
|
|
15902
|
+
* Normalized 0..1 trajectory envelope (min/max over every position bbox,
|
|
15903
|
+
* divided by the track's detection-frame dims), computed at persist time.
|
|
15904
|
+
* Absent when the frame dims were unknown when the track was persisted
|
|
15905
|
+
* (legacy rows / dims-less sources) and on active (in-RAM) tracks.
|
|
15906
|
+
*/
|
|
15907
|
+
var TrackEnvelopeSchema = object({
|
|
15908
|
+
minX: number(),
|
|
15909
|
+
minY: number(),
|
|
15910
|
+
maxX: number(),
|
|
15911
|
+
maxY: number()
|
|
15912
|
+
});
|
|
15913
|
+
/**
|
|
15914
|
+
* Row projection for track list queries. `full` (default) returns the
|
|
15915
|
+
* complete Track including the frame-rate `positions[]` history and the
|
|
15916
|
+
* `snapshots[]` references — megabytes across a page of tracks. `slim`
|
|
15917
|
+
* keeps every scalar the list surfaces actually render (ids, class(es),
|
|
15918
|
+
* label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
|
|
15919
|
+
* zonesVisited, bestEventId, envelope) and returns `positions` /
|
|
15920
|
+
* `snapshots` as EMPTY arrays — detail views re-fetch the full row via
|
|
15921
|
+
* `getTrack`. Mirrors the event-store `projection` convention
|
|
15922
|
+
* (`getObjectEvents` et al.).
|
|
15923
|
+
*/
|
|
15924
|
+
var TrackProjectionSchema = _enum(["full", "slim"]);
|
|
15925
|
+
/**
|
|
15808
15926
|
* One audio-classification label heard on the track's camera while the
|
|
15809
15927
|
* track was alive, aggregated per label. An "episode" is one persisted
|
|
15810
15928
|
* audio event (the confident-classification path: score ≥ the device's
|
|
@@ -15855,7 +15973,11 @@ var TrackSchema = object({
|
|
|
15855
15973
|
/** Audio-classification labels heard on the camera during the track's
|
|
15856
15974
|
* life (score ≥ device `classificationMinScore`), aggregated per label.
|
|
15857
15975
|
* Absent on legacy rows / tracks with no confident audio. */
|
|
15858
|
-
audioLabels: array(TrackAudioLabelSchema).readonly().optional()
|
|
15976
|
+
audioLabels: array(TrackAudioLabelSchema).readonly().optional(),
|
|
15977
|
+
/** Normalized 0..1 trajectory envelope (see {@link TrackEnvelopeSchema}).
|
|
15978
|
+
* Populated from the persisted envelope columns on historical reads;
|
|
15979
|
+
* absent on legacy rows, dims-less tracks and active (in-RAM) tracks. */
|
|
15980
|
+
envelope: TrackEnvelopeSchema.optional()
|
|
15859
15981
|
});
|
|
15860
15982
|
var BaseEventFields = {
|
|
15861
15983
|
id: string(),
|
|
@@ -15965,11 +16087,13 @@ var MediaFileSchema = object({
|
|
|
15965
16087
|
sizeBytes: number(),
|
|
15966
16088
|
timestamp: number()
|
|
15967
16089
|
});
|
|
16090
|
+
var DEFAULT_EVENT_QUERY_LIMIT = 1e3;
|
|
16091
|
+
var MAX_EVENT_QUERY_LIMIT = 5e3;
|
|
15968
16092
|
var DeviceEventQueryInput = object({
|
|
15969
16093
|
deviceId: number(),
|
|
15970
16094
|
since: number().optional(),
|
|
15971
16095
|
until: number().optional(),
|
|
15972
|
-
limit: number().int().min(1).max(
|
|
16096
|
+
limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT),
|
|
15973
16097
|
/** `slim` drops heavy JSON fields (regions/bbox/zones) and carries an
|
|
15974
16098
|
* optional `mediaUrl` (populated by B5). `full` (default) keeps today's
|
|
15975
16099
|
* exact behaviour. Callers may omit this field — the store defaults to
|
|
@@ -15977,6 +16101,27 @@ var DeviceEventQueryInput = object({
|
|
|
15977
16101
|
projection: _enum(["full", "slim"]).optional()
|
|
15978
16102
|
});
|
|
15979
16103
|
var ObjectEventQueryInput = DeviceEventQueryInput.extend({ classFilter: string().optional() });
|
|
16104
|
+
var RecentTracksQueryInput = object({
|
|
16105
|
+
/** Devices to merge. An empty array yields `{ tracks: [], nextCursor: null }`. */
|
|
16106
|
+
deviceIds: array(number()),
|
|
16107
|
+
/** Window lower bound on `lastSeen` (inclusive). */
|
|
16108
|
+
since: number().optional(),
|
|
16109
|
+
/** Window upper bound on `lastSeen` (inclusive). */
|
|
16110
|
+
until: number().optional(),
|
|
16111
|
+
/** Page size. Default 200, max 1000. */
|
|
16112
|
+
limit: number().int().min(1).max(1e3).default(200),
|
|
16113
|
+
/** Opaque continuation cursor from a previous page's `nextCursor`.
|
|
16114
|
+
* Encodes the (lastSeen, trackId) sort position — treat as opaque. */
|
|
16115
|
+
cursor: string().optional(),
|
|
16116
|
+
/** See {@link TrackProjectionSchema}. Default `full`. */
|
|
16117
|
+
projection: TrackProjectionSchema.optional()
|
|
16118
|
+
});
|
|
16119
|
+
var RecentTracksPageSchema = object({
|
|
16120
|
+
/** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
|
|
16121
|
+
tracks: array(TrackSchema).readonly(),
|
|
16122
|
+
/** Cursor for the next page, or null when this page is the last. */
|
|
16123
|
+
nextCursor: string().nullable()
|
|
16124
|
+
});
|
|
15980
16125
|
var KeyEventQueryInput = object({
|
|
15981
16126
|
deviceId: number(),
|
|
15982
16127
|
/** Window lower bound (track firstSeen ≥ since). */
|
|
@@ -16059,11 +16204,25 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
16059
16204
|
deviceId: number(),
|
|
16060
16205
|
since: number().optional(),
|
|
16061
16206
|
until: number().optional(),
|
|
16062
|
-
limit: number().optional()
|
|
16063
|
-
|
|
16064
|
-
|
|
16065
|
-
|
|
16066
|
-
|
|
16207
|
+
limit: number().optional(),
|
|
16208
|
+
/** Spatial filter — only tracks whose trajectory intersects the zone
|
|
16209
|
+
* (normalized 0..1 rect or polygon). SQL-prefiltered on the persisted
|
|
16210
|
+
* envelope columns, then precisely tested per position. Tracks with
|
|
16211
|
+
* an unknown envelope (no frame dims at persist time) always match. */
|
|
16212
|
+
zone: TrackZoneFilterSchema.optional(),
|
|
16213
|
+
/** See {@link TrackProjectionSchema}. Default `full` (backward
|
|
16214
|
+
* compatible — omitting the field keeps today's exact behaviour). */
|
|
16215
|
+
projection: TrackProjectionSchema.optional()
|
|
16216
|
+
}), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
|
|
16217
|
+
kind: "mutation",
|
|
16218
|
+
auth: "admin"
|
|
16219
|
+
}), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({
|
|
16220
|
+
deviceId: number(),
|
|
16221
|
+
since: number().optional(),
|
|
16222
|
+
until: number().optional(),
|
|
16223
|
+
kinds: array(string()).optional(),
|
|
16224
|
+
limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
|
|
16225
|
+
}), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
|
|
16067
16226
|
deviceId: number(),
|
|
16068
16227
|
since: number(),
|
|
16069
16228
|
until: number(),
|
|
@@ -19264,7 +19423,12 @@ var RecordingStorageUsageSchema = object({
|
|
|
19264
19423
|
/**
|
|
19265
19424
|
* Result of locating footage at a wall-clock instant for one device/profile.
|
|
19266
19425
|
* `segment` carries the covering segment's window; `gap` reports the forward
|
|
19267
|
-
* nearest covered edge (`null` past the end of footage / when none exists)
|
|
19426
|
+
* nearest covered edge (`null` past the end of footage / when none exists)
|
|
19427
|
+
* and the backward covered edge `prevEndMs` (exclusive end of the nearest
|
|
19428
|
+
* footage behind the epoch; `null` when none — optional so older providers
|
|
19429
|
+
* that omit it stay valid). `prevEndMs` lets a backward frame-step hop the
|
|
19430
|
+
* small inter-segment cracks (durMs under-covers the span to the next
|
|
19431
|
+
* startMs by ~11-17 ms) instead of no-opping at a segment head.
|
|
19268
19432
|
*/
|
|
19269
19433
|
var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
|
|
19270
19434
|
kind: literal("segment"),
|
|
@@ -19273,7 +19437,8 @@ var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
|
|
|
19273
19437
|
bytes: number()
|
|
19274
19438
|
}), object({
|
|
19275
19439
|
kind: literal("gap"),
|
|
19276
|
-
nearestEdgeMs: number().nullable()
|
|
19440
|
+
nearestEdgeMs: number().nullable(),
|
|
19441
|
+
prevEndMs: number().nullable().optional()
|
|
19277
19442
|
})]);
|
|
19278
19443
|
/** Raw bytes of one finalized footage segment (read off disk on the recording node). */
|
|
19279
19444
|
var ReadSegmentBytesResultSchema = object({ data: _instanceof(Uint8Array) });
|
|
@@ -20842,6 +21007,12 @@ Object.freeze({
|
|
|
20842
21007
|
addonId: null,
|
|
20843
21008
|
access: "view"
|
|
20844
21009
|
},
|
|
21010
|
+
"deviceManager.getLinkedDevices": {
|
|
21011
|
+
capName: "device-manager",
|
|
21012
|
+
capScope: "system",
|
|
21013
|
+
addonId: null,
|
|
21014
|
+
access: "view"
|
|
21015
|
+
},
|
|
20845
21016
|
"deviceManager.getRoleDisplayDefaults": {
|
|
20846
21017
|
capName: "device-manager",
|
|
20847
21018
|
capScope: "system",
|
|
@@ -22396,6 +22567,12 @@ Object.freeze({
|
|
|
22396
22567
|
addonId: null,
|
|
22397
22568
|
access: "view"
|
|
22398
22569
|
},
|
|
22570
|
+
"pipelineAnalytics.getSensorEvents": {
|
|
22571
|
+
capName: "pipeline-analytics",
|
|
22572
|
+
capScope: "device",
|
|
22573
|
+
addonId: null,
|
|
22574
|
+
access: "view"
|
|
22575
|
+
},
|
|
22399
22576
|
"pipelineAnalytics.getTrack": {
|
|
22400
22577
|
capName: "pipeline-analytics",
|
|
22401
22578
|
capScope: "device",
|
|
@@ -22408,6 +22585,18 @@ Object.freeze({
|
|
|
22408
22585
|
addonId: null,
|
|
22409
22586
|
access: "view"
|
|
22410
22587
|
},
|
|
22588
|
+
"pipelineAnalytics.listEventKinds": {
|
|
22589
|
+
capName: "pipeline-analytics",
|
|
22590
|
+
capScope: "device",
|
|
22591
|
+
addonId: null,
|
|
22592
|
+
access: "view"
|
|
22593
|
+
},
|
|
22594
|
+
"pipelineAnalytics.listRecentTracks": {
|
|
22595
|
+
capName: "pipeline-analytics",
|
|
22596
|
+
capScope: "device",
|
|
22597
|
+
addonId: null,
|
|
22598
|
+
access: "view"
|
|
22599
|
+
},
|
|
22411
22600
|
"pipelineAnalytics.listTracks": {
|
|
22412
22601
|
capName: "pipeline-analytics",
|
|
22413
22602
|
capScope: "device",
|
|
@@ -25608,7 +25797,7 @@ function entryForRef(ref) {
|
|
|
25608
25797
|
};
|
|
25609
25798
|
}
|
|
25610
25799
|
//#endregion
|
|
25611
|
-
//#region ../system/dist/model-download-service-
|
|
25800
|
+
//#region ../system/dist/model-download-service-Cp9f4dk6.mjs
|
|
25612
25801
|
function isNonEmptyFile(filePath) {
|
|
25613
25802
|
return node_fs.existsSync(filePath) && node_fs.statSync(filePath).size > 0;
|
|
25614
25803
|
}
|
package/dist/addon.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { createReadStream } from "node:fs";
|
|
|
5
5
|
import * as fsp from "node:fs/promises";
|
|
6
6
|
import { spawn } from "node:child_process";
|
|
7
7
|
import { createServer } from "node:net";
|
|
8
|
-
//#region ../types/dist/event-category-
|
|
8
|
+
//#region ../types/dist/event-category-H4AVePnn.mjs
|
|
9
9
|
var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
10
10
|
EventCategory["SystemBoot"] = "system.boot";
|
|
11
11
|
EventCategory["SystemAddonsReady"] = "system.addons-ready";
|
|
@@ -155,6 +155,9 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
155
155
|
EventCategory["RecordingSegmentWritten"] = "recording.segment.written";
|
|
156
156
|
EventCategory["RecordingPolicyFallback"] = "recording.policy.fallback";
|
|
157
157
|
EventCategory["RecordingRetentionCompleted"] = "recording.retention.completed";
|
|
158
|
+
/** Runner-sampled scrub thumbnail (~1/5 s/camera). Telemetry (D8): a lost
|
|
159
|
+
* thumb is a scrub gap the recorder's keyframe backfill covers. */
|
|
160
|
+
EventCategory["RecordingThumbSampled"] = "recording.thumb-sampled";
|
|
158
161
|
EventCategory["DetectionEvent"] = "detection.event";
|
|
159
162
|
EventCategory["SessionTrackNew"] = "session.track.new";
|
|
160
163
|
EventCategory["SessionTrackExpired"] = "session.track.expired";
|
|
@@ -7224,6 +7227,24 @@ var RecordingRetentionSchema = object({
|
|
|
7224
7227
|
maxSizeGb: number().min(0).optional()
|
|
7225
7228
|
});
|
|
7226
7229
|
/**
|
|
7230
|
+
* Scrub-thumbnail fidelity preset — the single per-camera selector bundling the
|
|
7231
|
+
* sprite tile RESOLUTION + JPEG QUALITY the recorder packs timeline-scrub
|
|
7232
|
+
* previews at. Five graduated steps; absent on a config = `standard` (the
|
|
7233
|
+
* shipped default, matching `sheet-geometry`/`sheet-composer`).
|
|
7234
|
+
*
|
|
7235
|
+
* Existing sheets are IMMUTABLE — a changed preset applies to NEW windows only.
|
|
7236
|
+
* Each window's index sidecar carries its own tile dims, so a camera whose
|
|
7237
|
+
* preset changed over time renders every historical window at the dims it was
|
|
7238
|
+
* written with.
|
|
7239
|
+
*/
|
|
7240
|
+
var ScrubThumbnailPresetSchema = _enum([
|
|
7241
|
+
"minimal",
|
|
7242
|
+
"low",
|
|
7243
|
+
"standard",
|
|
7244
|
+
"high",
|
|
7245
|
+
"max"
|
|
7246
|
+
]);
|
|
7247
|
+
/**
|
|
7227
7248
|
* The full per-camera recording intent — the wire shape of a RecordingTarget.
|
|
7228
7249
|
*
|
|
7229
7250
|
* `mode` is the authoritative storage choice; `schedule`/`triggers`/`pre`/`post`
|
|
@@ -7260,7 +7281,14 @@ var RecordingConfigSchema = object({
|
|
|
7260
7281
|
* derived into bands once via `migrateConfigToBands`.
|
|
7261
7282
|
*/
|
|
7262
7283
|
bands: array(RecordingBandSchema).optional(),
|
|
7263
|
-
retention: RecordingRetentionSchema.optional()
|
|
7284
|
+
retention: RecordingRetentionSchema.optional(),
|
|
7285
|
+
/**
|
|
7286
|
+
* Per-camera scrub-thumbnail fidelity preset (resolution + JPEG quality for
|
|
7287
|
+
* timeline-scrub sprite previews). Absent = `standard`. Applies to NEW
|
|
7288
|
+
* windows only — existing sheets are immutable, and each window's index
|
|
7289
|
+
* carries its own tile dims so mixed-preset history renders correctly.
|
|
7290
|
+
*/
|
|
7291
|
+
scrubThumbnails: ScrubThumbnailPresetSchema.optional()
|
|
7264
7292
|
});
|
|
7265
7293
|
/**
|
|
7266
7294
|
* `StorageLocationType` — an addon-declared id that identifies the *kind* of
|
|
@@ -9849,7 +9877,7 @@ object({
|
|
|
9849
9877
|
deviceId: number(),
|
|
9850
9878
|
timestamp: number()
|
|
9851
9879
|
});
|
|
9852
|
-
DeviceType.Button;
|
|
9880
|
+
DeviceType.Button, DeviceType.Camera;
|
|
9853
9881
|
/**
|
|
9854
9882
|
* Enum-state sensor — a string value picked from a finite option set.
|
|
9855
9883
|
* Drives HA `sensor` entries with `state_class: enum` (HVAC action
|
|
@@ -14219,6 +14247,14 @@ var ConfigEntrySchema = object({
|
|
|
14219
14247
|
value: unknown(),
|
|
14220
14248
|
description: string().optional()
|
|
14221
14249
|
});
|
|
14250
|
+
var DeviceLinkModeSchema = _enum(["auto", "manual"]);
|
|
14251
|
+
/** One resolved linked device — the compact projection consumers need. */
|
|
14252
|
+
var LinkedDeviceSchema = object({
|
|
14253
|
+
deviceId: number(),
|
|
14254
|
+
name: string(),
|
|
14255
|
+
location: string().nullable(),
|
|
14256
|
+
features: array(string())
|
|
14257
|
+
});
|
|
14222
14258
|
var SavedDeviceRowSchema = object({
|
|
14223
14259
|
/** Numeric id reserved at allocateDeviceId time. */
|
|
14224
14260
|
id: number(),
|
|
@@ -14440,7 +14476,10 @@ method(object({
|
|
|
14440
14476
|
}), _void(), {
|
|
14441
14477
|
kind: "mutation",
|
|
14442
14478
|
auth: "admin"
|
|
14443
|
-
}), method(object({ addonId: string() }), array(SavedDeviceRowSchema)), method(object({ addonId: string().optional() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }),
|
|
14479
|
+
}), method(object({ addonId: string() }), array(SavedDeviceRowSchema)), method(object({ addonId: string().optional() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
|
|
14480
|
+
mode: DeviceLinkModeSchema,
|
|
14481
|
+
devices: array(LinkedDeviceSchema)
|
|
14482
|
+
})), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
|
|
14444
14483
|
deviceId: number(),
|
|
14445
14484
|
values: record(string(), unknown())
|
|
14446
14485
|
}), object({ success: literal(true) }), {
|
|
@@ -15754,6 +15793,61 @@ var EventKindSchema = _enum([
|
|
|
15754
15793
|
"object",
|
|
15755
15794
|
"audio"
|
|
15756
15795
|
]);
|
|
15796
|
+
/**
|
|
15797
|
+
* Spatial filter for `listTracks` — the rect + polygon variants of the shared
|
|
15798
|
+
* MaskShape vocabulary (see `mask-shape.ts`). Coordinates are NORMALIZED 0..1
|
|
15799
|
+
* of the camera frame (top-left origin), matching the drawing-plane editor.
|
|
15800
|
+
*/
|
|
15801
|
+
var TrackZoneFilterSchema = discriminatedUnion("kind", [MaskRectShapeSchema, MaskPolygonShapeSchema]);
|
|
15802
|
+
/** Closed icon vocabulary so clients render a known glyph per kind. */
|
|
15803
|
+
var EventKindIconSchema = _enum([
|
|
15804
|
+
"motion",
|
|
15805
|
+
"audio",
|
|
15806
|
+
"person",
|
|
15807
|
+
"vehicle",
|
|
15808
|
+
"animal",
|
|
15809
|
+
"door",
|
|
15810
|
+
"pir",
|
|
15811
|
+
"smoke",
|
|
15812
|
+
"water",
|
|
15813
|
+
"button",
|
|
15814
|
+
"generic"
|
|
15815
|
+
]);
|
|
15816
|
+
var EventKindCategorySchema = _enum([
|
|
15817
|
+
"motion",
|
|
15818
|
+
"audio",
|
|
15819
|
+
"detection",
|
|
15820
|
+
"sensor",
|
|
15821
|
+
"custom"
|
|
15822
|
+
]);
|
|
15823
|
+
var EventKindDescriptorSchema = object({
|
|
15824
|
+
/** Stable kind id (e.g. 'motion', 'person', 'contact'). */
|
|
15825
|
+
kind: string(),
|
|
15826
|
+
label: string(),
|
|
15827
|
+
/** Hex color for timeline/legend rendering. */
|
|
15828
|
+
color: string(),
|
|
15829
|
+
icon: EventKindIconSchema,
|
|
15830
|
+
category: EventKindCategorySchema,
|
|
15831
|
+
/** Which cap + device contributes this kind. For built-ins the camera
|
|
15832
|
+
* itself; for sensor kinds the LINKED source device. */
|
|
15833
|
+
source: object({
|
|
15834
|
+
capName: string(),
|
|
15835
|
+
deviceId: number()
|
|
15836
|
+
})
|
|
15837
|
+
});
|
|
15838
|
+
var SensorEventSchema = object({
|
|
15839
|
+
id: string(),
|
|
15840
|
+
/** The CAMERA the event is attributed to (a sensor linked to N cameras
|
|
15841
|
+
* yields N rows, one per camera). */
|
|
15842
|
+
deviceId: number(),
|
|
15843
|
+
/** The linked sensor device whose state changed. */
|
|
15844
|
+
sourceDeviceId: number(),
|
|
15845
|
+
/** Event kind id — matches an `EventKindDescriptor.kind`. */
|
|
15846
|
+
kind: string(),
|
|
15847
|
+
/** Snapshot of the sensor cap's runtime-state slice at the change. */
|
|
15848
|
+
value: record(string(), unknown()).nullable(),
|
|
15849
|
+
timestamp: number()
|
|
15850
|
+
});
|
|
15757
15851
|
var TrackPositionSchema = object({
|
|
15758
15852
|
x: number(),
|
|
15759
15853
|
y: number(),
|
|
@@ -15767,6 +15861,30 @@ var TrackSnapshotSchema = object({
|
|
|
15767
15861
|
mediaKey: string()
|
|
15768
15862
|
});
|
|
15769
15863
|
/**
|
|
15864
|
+
* Normalized 0..1 trajectory envelope (min/max over every position bbox,
|
|
15865
|
+
* divided by the track's detection-frame dims), computed at persist time.
|
|
15866
|
+
* Absent when the frame dims were unknown when the track was persisted
|
|
15867
|
+
* (legacy rows / dims-less sources) and on active (in-RAM) tracks.
|
|
15868
|
+
*/
|
|
15869
|
+
var TrackEnvelopeSchema = object({
|
|
15870
|
+
minX: number(),
|
|
15871
|
+
minY: number(),
|
|
15872
|
+
maxX: number(),
|
|
15873
|
+
maxY: number()
|
|
15874
|
+
});
|
|
15875
|
+
/**
|
|
15876
|
+
* Row projection for track list queries. `full` (default) returns the
|
|
15877
|
+
* complete Track including the frame-rate `positions[]` history and the
|
|
15878
|
+
* `snapshots[]` references — megabytes across a page of tracks. `slim`
|
|
15879
|
+
* keeps every scalar the list surfaces actually render (ids, class(es),
|
|
15880
|
+
* label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
|
|
15881
|
+
* zonesVisited, bestEventId, envelope) and returns `positions` /
|
|
15882
|
+
* `snapshots` as EMPTY arrays — detail views re-fetch the full row via
|
|
15883
|
+
* `getTrack`. Mirrors the event-store `projection` convention
|
|
15884
|
+
* (`getObjectEvents` et al.).
|
|
15885
|
+
*/
|
|
15886
|
+
var TrackProjectionSchema = _enum(["full", "slim"]);
|
|
15887
|
+
/**
|
|
15770
15888
|
* One audio-classification label heard on the track's camera while the
|
|
15771
15889
|
* track was alive, aggregated per label. An "episode" is one persisted
|
|
15772
15890
|
* audio event (the confident-classification path: score ≥ the device's
|
|
@@ -15817,7 +15935,11 @@ var TrackSchema = object({
|
|
|
15817
15935
|
/** Audio-classification labels heard on the camera during the track's
|
|
15818
15936
|
* life (score ≥ device `classificationMinScore`), aggregated per label.
|
|
15819
15937
|
* Absent on legacy rows / tracks with no confident audio. */
|
|
15820
|
-
audioLabels: array(TrackAudioLabelSchema).readonly().optional()
|
|
15938
|
+
audioLabels: array(TrackAudioLabelSchema).readonly().optional(),
|
|
15939
|
+
/** Normalized 0..1 trajectory envelope (see {@link TrackEnvelopeSchema}).
|
|
15940
|
+
* Populated from the persisted envelope columns on historical reads;
|
|
15941
|
+
* absent on legacy rows, dims-less tracks and active (in-RAM) tracks. */
|
|
15942
|
+
envelope: TrackEnvelopeSchema.optional()
|
|
15821
15943
|
});
|
|
15822
15944
|
var BaseEventFields = {
|
|
15823
15945
|
id: string(),
|
|
@@ -15927,11 +16049,13 @@ var MediaFileSchema = object({
|
|
|
15927
16049
|
sizeBytes: number(),
|
|
15928
16050
|
timestamp: number()
|
|
15929
16051
|
});
|
|
16052
|
+
var DEFAULT_EVENT_QUERY_LIMIT = 1e3;
|
|
16053
|
+
var MAX_EVENT_QUERY_LIMIT = 5e3;
|
|
15930
16054
|
var DeviceEventQueryInput = object({
|
|
15931
16055
|
deviceId: number(),
|
|
15932
16056
|
since: number().optional(),
|
|
15933
16057
|
until: number().optional(),
|
|
15934
|
-
limit: number().int().min(1).max(
|
|
16058
|
+
limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT),
|
|
15935
16059
|
/** `slim` drops heavy JSON fields (regions/bbox/zones) and carries an
|
|
15936
16060
|
* optional `mediaUrl` (populated by B5). `full` (default) keeps today's
|
|
15937
16061
|
* exact behaviour. Callers may omit this field — the store defaults to
|
|
@@ -15939,6 +16063,27 @@ var DeviceEventQueryInput = object({
|
|
|
15939
16063
|
projection: _enum(["full", "slim"]).optional()
|
|
15940
16064
|
});
|
|
15941
16065
|
var ObjectEventQueryInput = DeviceEventQueryInput.extend({ classFilter: string().optional() });
|
|
16066
|
+
var RecentTracksQueryInput = object({
|
|
16067
|
+
/** Devices to merge. An empty array yields `{ tracks: [], nextCursor: null }`. */
|
|
16068
|
+
deviceIds: array(number()),
|
|
16069
|
+
/** Window lower bound on `lastSeen` (inclusive). */
|
|
16070
|
+
since: number().optional(),
|
|
16071
|
+
/** Window upper bound on `lastSeen` (inclusive). */
|
|
16072
|
+
until: number().optional(),
|
|
16073
|
+
/** Page size. Default 200, max 1000. */
|
|
16074
|
+
limit: number().int().min(1).max(1e3).default(200),
|
|
16075
|
+
/** Opaque continuation cursor from a previous page's `nextCursor`.
|
|
16076
|
+
* Encodes the (lastSeen, trackId) sort position — treat as opaque. */
|
|
16077
|
+
cursor: string().optional(),
|
|
16078
|
+
/** See {@link TrackProjectionSchema}. Default `full`. */
|
|
16079
|
+
projection: TrackProjectionSchema.optional()
|
|
16080
|
+
});
|
|
16081
|
+
var RecentTracksPageSchema = object({
|
|
16082
|
+
/** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
|
|
16083
|
+
tracks: array(TrackSchema).readonly(),
|
|
16084
|
+
/** Cursor for the next page, or null when this page is the last. */
|
|
16085
|
+
nextCursor: string().nullable()
|
|
16086
|
+
});
|
|
15942
16087
|
var KeyEventQueryInput = object({
|
|
15943
16088
|
deviceId: number(),
|
|
15944
16089
|
/** Window lower bound (track firstSeen ≥ since). */
|
|
@@ -16021,11 +16166,25 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
16021
16166
|
deviceId: number(),
|
|
16022
16167
|
since: number().optional(),
|
|
16023
16168
|
until: number().optional(),
|
|
16024
|
-
limit: number().optional()
|
|
16025
|
-
|
|
16026
|
-
|
|
16027
|
-
|
|
16028
|
-
|
|
16169
|
+
limit: number().optional(),
|
|
16170
|
+
/** Spatial filter — only tracks whose trajectory intersects the zone
|
|
16171
|
+
* (normalized 0..1 rect or polygon). SQL-prefiltered on the persisted
|
|
16172
|
+
* envelope columns, then precisely tested per position. Tracks with
|
|
16173
|
+
* an unknown envelope (no frame dims at persist time) always match. */
|
|
16174
|
+
zone: TrackZoneFilterSchema.optional(),
|
|
16175
|
+
/** See {@link TrackProjectionSchema}. Default `full` (backward
|
|
16176
|
+
* compatible — omitting the field keeps today's exact behaviour). */
|
|
16177
|
+
projection: TrackProjectionSchema.optional()
|
|
16178
|
+
}), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
|
|
16179
|
+
kind: "mutation",
|
|
16180
|
+
auth: "admin"
|
|
16181
|
+
}), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({
|
|
16182
|
+
deviceId: number(),
|
|
16183
|
+
since: number().optional(),
|
|
16184
|
+
until: number().optional(),
|
|
16185
|
+
kinds: array(string()).optional(),
|
|
16186
|
+
limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
|
|
16187
|
+
}), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
|
|
16029
16188
|
deviceId: number(),
|
|
16030
16189
|
since: number(),
|
|
16031
16190
|
until: number(),
|
|
@@ -19226,7 +19385,12 @@ var RecordingStorageUsageSchema = object({
|
|
|
19226
19385
|
/**
|
|
19227
19386
|
* Result of locating footage at a wall-clock instant for one device/profile.
|
|
19228
19387
|
* `segment` carries the covering segment's window; `gap` reports the forward
|
|
19229
|
-
* nearest covered edge (`null` past the end of footage / when none exists)
|
|
19388
|
+
* nearest covered edge (`null` past the end of footage / when none exists)
|
|
19389
|
+
* and the backward covered edge `prevEndMs` (exclusive end of the nearest
|
|
19390
|
+
* footage behind the epoch; `null` when none — optional so older providers
|
|
19391
|
+
* that omit it stay valid). `prevEndMs` lets a backward frame-step hop the
|
|
19392
|
+
* small inter-segment cracks (durMs under-covers the span to the next
|
|
19393
|
+
* startMs by ~11-17 ms) instead of no-opping at a segment head.
|
|
19230
19394
|
*/
|
|
19231
19395
|
var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
|
|
19232
19396
|
kind: literal("segment"),
|
|
@@ -19235,7 +19399,8 @@ var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
|
|
|
19235
19399
|
bytes: number()
|
|
19236
19400
|
}), object({
|
|
19237
19401
|
kind: literal("gap"),
|
|
19238
|
-
nearestEdgeMs: number().nullable()
|
|
19402
|
+
nearestEdgeMs: number().nullable(),
|
|
19403
|
+
prevEndMs: number().nullable().optional()
|
|
19239
19404
|
})]);
|
|
19240
19405
|
/** Raw bytes of one finalized footage segment (read off disk on the recording node). */
|
|
19241
19406
|
var ReadSegmentBytesResultSchema = object({ data: _instanceof(Uint8Array) });
|
|
@@ -20804,6 +20969,12 @@ Object.freeze({
|
|
|
20804
20969
|
addonId: null,
|
|
20805
20970
|
access: "view"
|
|
20806
20971
|
},
|
|
20972
|
+
"deviceManager.getLinkedDevices": {
|
|
20973
|
+
capName: "device-manager",
|
|
20974
|
+
capScope: "system",
|
|
20975
|
+
addonId: null,
|
|
20976
|
+
access: "view"
|
|
20977
|
+
},
|
|
20807
20978
|
"deviceManager.getRoleDisplayDefaults": {
|
|
20808
20979
|
capName: "device-manager",
|
|
20809
20980
|
capScope: "system",
|
|
@@ -22358,6 +22529,12 @@ Object.freeze({
|
|
|
22358
22529
|
addonId: null,
|
|
22359
22530
|
access: "view"
|
|
22360
22531
|
},
|
|
22532
|
+
"pipelineAnalytics.getSensorEvents": {
|
|
22533
|
+
capName: "pipeline-analytics",
|
|
22534
|
+
capScope: "device",
|
|
22535
|
+
addonId: null,
|
|
22536
|
+
access: "view"
|
|
22537
|
+
},
|
|
22361
22538
|
"pipelineAnalytics.getTrack": {
|
|
22362
22539
|
capName: "pipeline-analytics",
|
|
22363
22540
|
capScope: "device",
|
|
@@ -22370,6 +22547,18 @@ Object.freeze({
|
|
|
22370
22547
|
addonId: null,
|
|
22371
22548
|
access: "view"
|
|
22372
22549
|
},
|
|
22550
|
+
"pipelineAnalytics.listEventKinds": {
|
|
22551
|
+
capName: "pipeline-analytics",
|
|
22552
|
+
capScope: "device",
|
|
22553
|
+
addonId: null,
|
|
22554
|
+
access: "view"
|
|
22555
|
+
},
|
|
22556
|
+
"pipelineAnalytics.listRecentTracks": {
|
|
22557
|
+
capName: "pipeline-analytics",
|
|
22558
|
+
capScope: "device",
|
|
22559
|
+
addonId: null,
|
|
22560
|
+
access: "view"
|
|
22561
|
+
},
|
|
22373
22562
|
"pipelineAnalytics.listTracks": {
|
|
22374
22563
|
capName: "pipeline-analytics",
|
|
22375
22564
|
capScope: "device",
|
|
@@ -25570,7 +25759,7 @@ function entryForRef(ref) {
|
|
|
25570
25759
|
};
|
|
25571
25760
|
}
|
|
25572
25761
|
//#endregion
|
|
25573
|
-
//#region ../system/dist/model-download-service-
|
|
25762
|
+
//#region ../system/dist/model-download-service-Cp9f4dk6.mjs
|
|
25574
25763
|
function isNonEmptyFile(filePath) {
|
|
25575
25764
|
return fs.existsSync(filePath) && fs.statSync(filePath).size > 0;
|
|
25576
25765
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-ai",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "AI addon for CamStack — the `llm` collection provider (cloud, LAN, and camstack-managed local llama.cpp profiles) plus the per-node `llm-runtime` managed executor.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|