@camstack/addon-export-alexa 1.2.57 → 1.2.59

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.
@@ -7886,7 +7886,8 @@ var OpsLogReasonSchema = _enum([
7886
7886
  "quota",
7887
7887
  "manual",
7888
7888
  "operator",
7889
- "maintenance"
7889
+ "maintenance",
7890
+ "orphaned-device"
7890
7891
  ]);
7891
7892
  /** One audit row, shared verbatim by both domains. */
7892
7893
  var OpsLogEntrySchema = object({
@@ -13044,10 +13045,39 @@ var DevicePersistConfigPayloadSchema = object({
13044
13045
  deviceId: number(),
13045
13046
  data: record(string(), unknown())
13046
13047
  });
13048
+ /** What a migration actually did, per switch. `unreachable` is a first-class
13049
+ * answer: a camera that could not be asked is not a camera that was silenced. */
13050
+ var MigrateSwitchOutcomeSchema = _enum([
13051
+ "off",
13052
+ "on",
13053
+ "not-offered",
13054
+ "unreachable"
13055
+ ]);
13056
+ var MigrateSwitchReportSchema = object({
13057
+ deviceId: number(),
13058
+ switchId: CameraSwitchIdSchema,
13059
+ outcome: MigrateSwitchOutcomeSchema,
13060
+ detail: string().optional()
13061
+ });
13062
+ var MigrateDeviceResultSchema = object({
13063
+ sourceId: number(),
13064
+ targetId: number(),
13065
+ switches: array(MigrateSwitchReportSchema).readonly(),
13066
+ /** Switches that could NOT be turned off on the source. Empty is the only
13067
+ * value meaning the replaced hardware is quiet. */
13068
+ sourceStillLive: array(CameraSwitchIdSchema).readonly(),
13069
+ swapped: boolean()
13070
+ });
13047
13071
  method(object({
13048
13072
  addonId: string(),
13049
13073
  stableId: string()
13050
- }), object({ id: number() }), { kind: "mutation" }), method(DeviceRegisterPayloadSchema, _void(), { kind: "mutation" }), method(DeviceRemovePayloadSchema, _void(), { kind: "mutation" }), method(DevicePersistConfigPayloadSchema, _void(), { kind: "mutation" }), method(object({ deviceId: number() }), record(string(), unknown())), method(object({ deviceId: number() }), record(string(), unknown())), method(object({ deviceId: number() }), DeviceMetaSchema.nullable()), method(object({
13074
+ }), object({ id: number() }), { kind: "mutation" }), method(object({
13075
+ sourceId: number(),
13076
+ targetId: number()
13077
+ }), MigrateDeviceResultSchema, {
13078
+ kind: "mutation",
13079
+ auth: "admin"
13080
+ }), method(DeviceRegisterPayloadSchema, _void(), { kind: "mutation" }), method(DeviceRemovePayloadSchema, _void(), { kind: "mutation" }), method(DevicePersistConfigPayloadSchema, _void(), { kind: "mutation" }), method(object({ deviceId: number() }), record(string(), unknown())), method(object({ deviceId: number() }), record(string(), unknown())), method(object({ deviceId: number() }), DeviceMetaSchema.nullable()), method(object({
13051
13081
  deviceId: number(),
13052
13082
  name: string()
13053
13083
  }), _void(), {
@@ -19160,6 +19190,34 @@ var KeyEventsForDeviceSchema = object({
19160
19190
  deviceId: number(),
19161
19191
  events: array(KeyEventSchema).readonly()
19162
19192
  });
19193
+ /** One non-empty bucket of the timeline histogram. */
19194
+ var EventDensityBucketSchema = object({
19195
+ bucketStart: number(),
19196
+ motion: number().int(),
19197
+ object: number().int(),
19198
+ audio: number().int()
19199
+ });
19200
+ /**
19201
+ * One camera's row in a `getEventDensityBatch` answer.
19202
+ *
19203
+ * TWO facts, and the timeline draws them differently:
19204
+ *
19205
+ * - `read: 'read'` — the histogram for this camera. `buckets: []` is a real
19206
+ * claim: read, and nothing happened in the window.
19207
+ * - `read: 'unreadable'` — nobody could answer for this camera. `buckets` is
19208
+ * empty and that emptiness means NOTHING; the timeline must render unknown,
19209
+ * not quiet.
19210
+ *
19211
+ * Fanned out per camera the difference was free — one query rejected while the
19212
+ * others resolved — and collapsing to a batch is the exact moment it is lost
19213
+ * silently. Every requested id gets a row: a caller that asked for twelve and
19214
+ * got eleven cannot tell which one is missing, or that any is.
19215
+ */
19216
+ var EventDensityForDeviceSchema = object({
19217
+ deviceId: number(),
19218
+ read: _enum(["read", "unreadable"]),
19219
+ buckets: array(EventDensityBucketSchema).readonly()
19220
+ });
19163
19221
  object({
19164
19222
  trackId: string(),
19165
19223
  className: string(),
@@ -19470,12 +19528,12 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19470
19528
  since: number(),
19471
19529
  until: number(),
19472
19530
  bucketMs: number().int().positive()
19473
- }), array(object({
19474
- bucketStart: number(),
19475
- motion: number().int(),
19476
- object: number().int(),
19477
- audio: number().int()
19478
- })).readonly()), method(object({
19531
+ }), array(EventDensityBucketSchema).readonly()), method(object({
19532
+ deviceIds: array(number()).min(1).max(200),
19533
+ since: number(),
19534
+ until: number(),
19535
+ bucketMs: number().int().positive()
19536
+ }), array(EventDensityForDeviceSchema).readonly()), method(object({
19479
19537
  deviceId: number(),
19480
19538
  cutoffMs: number()
19481
19539
  }), object({
@@ -26785,6 +26843,39 @@ var RecordingDaysSchema = object({
26785
26843
  /** Local-midnight epochs (UTC ms) of days that have ≥1 recorded segment. */
26786
26844
  days: array(number())
26787
26845
  });
26846
+ /**
26847
+ * One camera's row in a `getAvailabilityBatch` answer.
26848
+ *
26849
+ * `ranges` is EXACTLY what `getAvailability` returns for that camera — the
26850
+ * batch collapses the transport, not the work — plus the one thing the singular
26851
+ * method never had to say:
26852
+ *
26853
+ * - `read: 'read'` — answered. `ranges: []` means "read, and this camera has
26854
+ * no footage in the window", which is a real claim.
26855
+ * - `read: 'unreadable'` — the recorder could not answer for this camera (its
26856
+ * calendar rung threw, its location is unmounted). `ranges` is empty and
26857
+ * that emptiness means NOTHING.
26858
+ *
26859
+ * A timeline that renders the second as the first tells an operator there is no
26860
+ * footage when the truth is that nobody looked.
26861
+ */
26862
+ var RecordingAvailabilityForDeviceSchema = object({
26863
+ deviceId: number(),
26864
+ read: _enum(["read", "unreadable"]),
26865
+ ranges: array(RecordingRangeSchema).readonly()
26866
+ });
26867
+ /**
26868
+ * One camera's row in a `getDaysWithRecordingsBatch` answer. Same rule as
26869
+ * {@link RecordingAvailabilityForDeviceSchema}: `days: []` on a `'read'` row is
26870
+ * "no footage in this month", `read: 'unreadable'` is "nobody could look", and
26871
+ * the date-picker's day dots must not spell them the same.
26872
+ */
26873
+ var RecordingDaysForDeviceSchema = object({
26874
+ deviceId: number(),
26875
+ read: _enum(["read", "unreadable"]),
26876
+ /** Local-midnight epochs (UTC ms) of days that have ≥1 recorded segment. */
26877
+ days: array(number()).readonly()
26878
+ });
26788
26879
  var RecordingManifestSchema = object({
26789
26880
  deviceId: number(),
26790
26881
  /** Local filesystem path to the master playlist; null when no recording exists for the requested range. */
@@ -27019,6 +27110,13 @@ method(object({
27019
27110
  }), RecordingAvailabilitySchema, {
27020
27111
  kind: "query",
27021
27112
  auth: "protected"
27113
+ }), method(object({
27114
+ deviceIds: array(number()).min(1).max(200),
27115
+ fromMs: number(),
27116
+ toMs: number()
27117
+ }), array(RecordingAvailabilityForDeviceSchema).readonly(), {
27118
+ kind: "query",
27119
+ auth: "protected"
27022
27120
  }), method(object({
27023
27121
  deviceId: number(),
27024
27122
  fromMs: number(),
@@ -27027,6 +27125,14 @@ method(object({
27027
27125
  }), RecordingDaysSchema, {
27028
27126
  kind: "query",
27029
27127
  auth: "protected"
27128
+ }), method(object({
27129
+ deviceIds: array(number()).min(1).max(200),
27130
+ fromMs: number(),
27131
+ toMs: number(),
27132
+ tzOffsetMinutes: number()
27133
+ }), array(RecordingDaysForDeviceSchema).readonly(), {
27134
+ kind: "query",
27135
+ auth: "protected"
27030
27136
  }), method(object({
27031
27137
  deviceId: number(),
27032
27138
  fromMs: number(),
@@ -30490,6 +30596,12 @@ Object.freeze({
30490
30596
  addonId: null,
30491
30597
  access: "view"
30492
30598
  },
30599
+ "deviceManager.migrateDevice": {
30600
+ capName: "device-manager",
30601
+ capScope: "system",
30602
+ addonId: null,
30603
+ access: "create"
30604
+ },
30493
30605
  "deviceManager.persistConfig": {
30494
30606
  capName: "device-manager",
30495
30607
  capScope: "system",
@@ -32224,6 +32336,12 @@ Object.freeze({
32224
32336
  addonId: null,
32225
32337
  access: "view"
32226
32338
  },
32339
+ "pipelineAnalytics.getEventDensityBatch": {
32340
+ capName: "pipeline-analytics",
32341
+ capScope: "device",
32342
+ addonId: null,
32343
+ access: "view"
32344
+ },
32227
32345
  "pipelineAnalytics.getEventMedia": {
32228
32346
  capName: "pipeline-analytics",
32229
32347
  capScope: "device",
@@ -33346,12 +33464,24 @@ Object.freeze({
33346
33464
  addonId: null,
33347
33465
  access: "view"
33348
33466
  },
33467
+ "recording.getAvailabilityBatch": {
33468
+ capName: "recording",
33469
+ capScope: "system",
33470
+ addonId: null,
33471
+ access: "view"
33472
+ },
33349
33473
  "recording.getDaysWithRecordings": {
33350
33474
  capName: "recording",
33351
33475
  capScope: "system",
33352
33476
  addonId: null,
33353
33477
  access: "view"
33354
33478
  },
33479
+ "recording.getDaysWithRecordingsBatch": {
33480
+ capName: "recording",
33481
+ capScope: "system",
33482
+ addonId: null,
33483
+ access: "view"
33484
+ },
33355
33485
  "recording.getDeviceConfig": {
33356
33486
  capName: "recording",
33357
33487
  capScope: "system",
@@ -35956,6 +36086,11 @@ Object.freeze({
35956
36086
  form: "single",
35957
36087
  optional: false
35958
36088
  }],
36089
+ "pipelineAnalytics.getEventDensityBatch": [{
36090
+ name: "deviceIds",
36091
+ form: "array",
36092
+ optional: false
36093
+ }],
35959
36094
  "pipelineAnalytics.getEventMedia": [{
35960
36095
  name: "deviceId",
35961
36096
  form: "single",
@@ -36371,11 +36506,21 @@ Object.freeze({
36371
36506
  form: "single",
36372
36507
  optional: false
36373
36508
  }],
36509
+ "recording.getAvailabilityBatch": [{
36510
+ name: "deviceIds",
36511
+ form: "array",
36512
+ optional: false
36513
+ }],
36374
36514
  "recording.getDaysWithRecordings": [{
36375
36515
  name: "deviceId",
36376
36516
  form: "single",
36377
36517
  optional: false
36378
36518
  }],
36519
+ "recording.getDaysWithRecordingsBatch": [{
36520
+ name: "deviceIds",
36521
+ form: "array",
36522
+ optional: false
36523
+ }],
36379
36524
  "recording.getDeviceConfig": [{
36380
36525
  name: "deviceId",
36381
36526
  form: "single",
@@ -7859,7 +7859,8 @@ var OpsLogReasonSchema = _enum([
7859
7859
  "quota",
7860
7860
  "manual",
7861
7861
  "operator",
7862
- "maintenance"
7862
+ "maintenance",
7863
+ "orphaned-device"
7863
7864
  ]);
7864
7865
  /** One audit row, shared verbatim by both domains. */
7865
7866
  var OpsLogEntrySchema = object({
@@ -13017,10 +13018,39 @@ var DevicePersistConfigPayloadSchema = object({
13017
13018
  deviceId: number(),
13018
13019
  data: record(string(), unknown())
13019
13020
  });
13021
+ /** What a migration actually did, per switch. `unreachable` is a first-class
13022
+ * answer: a camera that could not be asked is not a camera that was silenced. */
13023
+ var MigrateSwitchOutcomeSchema = _enum([
13024
+ "off",
13025
+ "on",
13026
+ "not-offered",
13027
+ "unreachable"
13028
+ ]);
13029
+ var MigrateSwitchReportSchema = object({
13030
+ deviceId: number(),
13031
+ switchId: CameraSwitchIdSchema,
13032
+ outcome: MigrateSwitchOutcomeSchema,
13033
+ detail: string().optional()
13034
+ });
13035
+ var MigrateDeviceResultSchema = object({
13036
+ sourceId: number(),
13037
+ targetId: number(),
13038
+ switches: array(MigrateSwitchReportSchema).readonly(),
13039
+ /** Switches that could NOT be turned off on the source. Empty is the only
13040
+ * value meaning the replaced hardware is quiet. */
13041
+ sourceStillLive: array(CameraSwitchIdSchema).readonly(),
13042
+ swapped: boolean()
13043
+ });
13020
13044
  method(object({
13021
13045
  addonId: string(),
13022
13046
  stableId: string()
13023
- }), object({ id: number() }), { kind: "mutation" }), method(DeviceRegisterPayloadSchema, _void(), { kind: "mutation" }), method(DeviceRemovePayloadSchema, _void(), { kind: "mutation" }), method(DevicePersistConfigPayloadSchema, _void(), { kind: "mutation" }), method(object({ deviceId: number() }), record(string(), unknown())), method(object({ deviceId: number() }), record(string(), unknown())), method(object({ deviceId: number() }), DeviceMetaSchema.nullable()), method(object({
13047
+ }), object({ id: number() }), { kind: "mutation" }), method(object({
13048
+ sourceId: number(),
13049
+ targetId: number()
13050
+ }), MigrateDeviceResultSchema, {
13051
+ kind: "mutation",
13052
+ auth: "admin"
13053
+ }), method(DeviceRegisterPayloadSchema, _void(), { kind: "mutation" }), method(DeviceRemovePayloadSchema, _void(), { kind: "mutation" }), method(DevicePersistConfigPayloadSchema, _void(), { kind: "mutation" }), method(object({ deviceId: number() }), record(string(), unknown())), method(object({ deviceId: number() }), record(string(), unknown())), method(object({ deviceId: number() }), DeviceMetaSchema.nullable()), method(object({
13024
13054
  deviceId: number(),
13025
13055
  name: string()
13026
13056
  }), _void(), {
@@ -19133,6 +19163,34 @@ var KeyEventsForDeviceSchema = object({
19133
19163
  deviceId: number(),
19134
19164
  events: array(KeyEventSchema).readonly()
19135
19165
  });
19166
+ /** One non-empty bucket of the timeline histogram. */
19167
+ var EventDensityBucketSchema = object({
19168
+ bucketStart: number(),
19169
+ motion: number().int(),
19170
+ object: number().int(),
19171
+ audio: number().int()
19172
+ });
19173
+ /**
19174
+ * One camera's row in a `getEventDensityBatch` answer.
19175
+ *
19176
+ * TWO facts, and the timeline draws them differently:
19177
+ *
19178
+ * - `read: 'read'` — the histogram for this camera. `buckets: []` is a real
19179
+ * claim: read, and nothing happened in the window.
19180
+ * - `read: 'unreadable'` — nobody could answer for this camera. `buckets` is
19181
+ * empty and that emptiness means NOTHING; the timeline must render unknown,
19182
+ * not quiet.
19183
+ *
19184
+ * Fanned out per camera the difference was free — one query rejected while the
19185
+ * others resolved — and collapsing to a batch is the exact moment it is lost
19186
+ * silently. Every requested id gets a row: a caller that asked for twelve and
19187
+ * got eleven cannot tell which one is missing, or that any is.
19188
+ */
19189
+ var EventDensityForDeviceSchema = object({
19190
+ deviceId: number(),
19191
+ read: _enum(["read", "unreadable"]),
19192
+ buckets: array(EventDensityBucketSchema).readonly()
19193
+ });
19136
19194
  object({
19137
19195
  trackId: string(),
19138
19196
  className: string(),
@@ -19443,12 +19501,12 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19443
19501
  since: number(),
19444
19502
  until: number(),
19445
19503
  bucketMs: number().int().positive()
19446
- }), array(object({
19447
- bucketStart: number(),
19448
- motion: number().int(),
19449
- object: number().int(),
19450
- audio: number().int()
19451
- })).readonly()), method(object({
19504
+ }), array(EventDensityBucketSchema).readonly()), method(object({
19505
+ deviceIds: array(number()).min(1).max(200),
19506
+ since: number(),
19507
+ until: number(),
19508
+ bucketMs: number().int().positive()
19509
+ }), array(EventDensityForDeviceSchema).readonly()), method(object({
19452
19510
  deviceId: number(),
19453
19511
  cutoffMs: number()
19454
19512
  }), object({
@@ -26758,6 +26816,39 @@ var RecordingDaysSchema = object({
26758
26816
  /** Local-midnight epochs (UTC ms) of days that have ≥1 recorded segment. */
26759
26817
  days: array(number())
26760
26818
  });
26819
+ /**
26820
+ * One camera's row in a `getAvailabilityBatch` answer.
26821
+ *
26822
+ * `ranges` is EXACTLY what `getAvailability` returns for that camera — the
26823
+ * batch collapses the transport, not the work — plus the one thing the singular
26824
+ * method never had to say:
26825
+ *
26826
+ * - `read: 'read'` — answered. `ranges: []` means "read, and this camera has
26827
+ * no footage in the window", which is a real claim.
26828
+ * - `read: 'unreadable'` — the recorder could not answer for this camera (its
26829
+ * calendar rung threw, its location is unmounted). `ranges` is empty and
26830
+ * that emptiness means NOTHING.
26831
+ *
26832
+ * A timeline that renders the second as the first tells an operator there is no
26833
+ * footage when the truth is that nobody looked.
26834
+ */
26835
+ var RecordingAvailabilityForDeviceSchema = object({
26836
+ deviceId: number(),
26837
+ read: _enum(["read", "unreadable"]),
26838
+ ranges: array(RecordingRangeSchema).readonly()
26839
+ });
26840
+ /**
26841
+ * One camera's row in a `getDaysWithRecordingsBatch` answer. Same rule as
26842
+ * {@link RecordingAvailabilityForDeviceSchema}: `days: []` on a `'read'` row is
26843
+ * "no footage in this month", `read: 'unreadable'` is "nobody could look", and
26844
+ * the date-picker's day dots must not spell them the same.
26845
+ */
26846
+ var RecordingDaysForDeviceSchema = object({
26847
+ deviceId: number(),
26848
+ read: _enum(["read", "unreadable"]),
26849
+ /** Local-midnight epochs (UTC ms) of days that have ≥1 recorded segment. */
26850
+ days: array(number()).readonly()
26851
+ });
26761
26852
  var RecordingManifestSchema = object({
26762
26853
  deviceId: number(),
26763
26854
  /** Local filesystem path to the master playlist; null when no recording exists for the requested range. */
@@ -26992,6 +27083,13 @@ method(object({
26992
27083
  }), RecordingAvailabilitySchema, {
26993
27084
  kind: "query",
26994
27085
  auth: "protected"
27086
+ }), method(object({
27087
+ deviceIds: array(number()).min(1).max(200),
27088
+ fromMs: number(),
27089
+ toMs: number()
27090
+ }), array(RecordingAvailabilityForDeviceSchema).readonly(), {
27091
+ kind: "query",
27092
+ auth: "protected"
26995
27093
  }), method(object({
26996
27094
  deviceId: number(),
26997
27095
  fromMs: number(),
@@ -27000,6 +27098,14 @@ method(object({
27000
27098
  }), RecordingDaysSchema, {
27001
27099
  kind: "query",
27002
27100
  auth: "protected"
27101
+ }), method(object({
27102
+ deviceIds: array(number()).min(1).max(200),
27103
+ fromMs: number(),
27104
+ toMs: number(),
27105
+ tzOffsetMinutes: number()
27106
+ }), array(RecordingDaysForDeviceSchema).readonly(), {
27107
+ kind: "query",
27108
+ auth: "protected"
27003
27109
  }), method(object({
27004
27110
  deviceId: number(),
27005
27111
  fromMs: number(),
@@ -30463,6 +30569,12 @@ Object.freeze({
30463
30569
  addonId: null,
30464
30570
  access: "view"
30465
30571
  },
30572
+ "deviceManager.migrateDevice": {
30573
+ capName: "device-manager",
30574
+ capScope: "system",
30575
+ addonId: null,
30576
+ access: "create"
30577
+ },
30466
30578
  "deviceManager.persistConfig": {
30467
30579
  capName: "device-manager",
30468
30580
  capScope: "system",
@@ -32197,6 +32309,12 @@ Object.freeze({
32197
32309
  addonId: null,
32198
32310
  access: "view"
32199
32311
  },
32312
+ "pipelineAnalytics.getEventDensityBatch": {
32313
+ capName: "pipeline-analytics",
32314
+ capScope: "device",
32315
+ addonId: null,
32316
+ access: "view"
32317
+ },
32200
32318
  "pipelineAnalytics.getEventMedia": {
32201
32319
  capName: "pipeline-analytics",
32202
32320
  capScope: "device",
@@ -33319,12 +33437,24 @@ Object.freeze({
33319
33437
  addonId: null,
33320
33438
  access: "view"
33321
33439
  },
33440
+ "recording.getAvailabilityBatch": {
33441
+ capName: "recording",
33442
+ capScope: "system",
33443
+ addonId: null,
33444
+ access: "view"
33445
+ },
33322
33446
  "recording.getDaysWithRecordings": {
33323
33447
  capName: "recording",
33324
33448
  capScope: "system",
33325
33449
  addonId: null,
33326
33450
  access: "view"
33327
33451
  },
33452
+ "recording.getDaysWithRecordingsBatch": {
33453
+ capName: "recording",
33454
+ capScope: "system",
33455
+ addonId: null,
33456
+ access: "view"
33457
+ },
33328
33458
  "recording.getDeviceConfig": {
33329
33459
  capName: "recording",
33330
33460
  capScope: "system",
@@ -35929,6 +36059,11 @@ Object.freeze({
35929
36059
  form: "single",
35930
36060
  optional: false
35931
36061
  }],
36062
+ "pipelineAnalytics.getEventDensityBatch": [{
36063
+ name: "deviceIds",
36064
+ form: "array",
36065
+ optional: false
36066
+ }],
35932
36067
  "pipelineAnalytics.getEventMedia": [{
35933
36068
  name: "deviceId",
35934
36069
  form: "single",
@@ -36344,11 +36479,21 @@ Object.freeze({
36344
36479
  form: "single",
36345
36480
  optional: false
36346
36481
  }],
36482
+ "recording.getAvailabilityBatch": [{
36483
+ name: "deviceIds",
36484
+ form: "array",
36485
+ optional: false
36486
+ }],
36347
36487
  "recording.getDaysWithRecordings": [{
36348
36488
  name: "deviceId",
36349
36489
  form: "single",
36350
36490
  optional: false
36351
36491
  }],
36492
+ "recording.getDaysWithRecordingsBatch": [{
36493
+ name: "deviceIds",
36494
+ form: "array",
36495
+ optional: false
36496
+ }],
36352
36497
  "recording.getDeviceConfig": [{
36353
36498
  name: "deviceId",
36354
36499
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-export-alexa",
3
- "version": "1.2.57",
3
+ "version": "1.2.59",
4
4
  "description": "Alexa Smart Home Skill export — hub-side OAuth provider + directive handler. The companion AWS Lambda forwards Alexa directives to this addon's /addon/export-alexa/directive endpoint.",
5
5
  "keywords": [
6
6
  "camstack",