@camstack/addon-terminal 0.1.56 → 0.1.58

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 CHANGED
@@ -7817,7 +7817,8 @@ var OpsLogReasonSchema = _enum([
7817
7817
  "quota",
7818
7818
  "manual",
7819
7819
  "operator",
7820
- "maintenance"
7820
+ "maintenance",
7821
+ "orphaned-device"
7821
7822
  ]);
7822
7823
  /** One audit row, shared verbatim by both domains. */
7823
7824
  var OpsLogEntrySchema = object({
@@ -13057,10 +13058,39 @@ var DevicePersistConfigPayloadSchema = object({
13057
13058
  deviceId: number(),
13058
13059
  data: record(string(), unknown())
13059
13060
  });
13061
+ /** What a migration actually did, per switch. `unreachable` is a first-class
13062
+ * answer: a camera that could not be asked is not a camera that was silenced. */
13063
+ var MigrateSwitchOutcomeSchema = _enum([
13064
+ "off",
13065
+ "on",
13066
+ "not-offered",
13067
+ "unreachable"
13068
+ ]);
13069
+ var MigrateSwitchReportSchema = object({
13070
+ deviceId: number(),
13071
+ switchId: CameraSwitchIdSchema,
13072
+ outcome: MigrateSwitchOutcomeSchema,
13073
+ detail: string().optional()
13074
+ });
13075
+ var MigrateDeviceResultSchema = object({
13076
+ sourceId: number(),
13077
+ targetId: number(),
13078
+ switches: array(MigrateSwitchReportSchema).readonly(),
13079
+ /** Switches that could NOT be turned off on the source. Empty is the only
13080
+ * value meaning the replaced hardware is quiet. */
13081
+ sourceStillLive: array(CameraSwitchIdSchema).readonly(),
13082
+ swapped: boolean()
13083
+ });
13060
13084
  method(object({
13061
13085
  addonId: string(),
13062
13086
  stableId: string()
13063
- }), 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({
13087
+ }), object({ id: number() }), { kind: "mutation" }), method(object({
13088
+ sourceId: number(),
13089
+ targetId: number()
13090
+ }), MigrateDeviceResultSchema, {
13091
+ kind: "mutation",
13092
+ auth: "admin"
13093
+ }), 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({
13064
13094
  deviceId: number(),
13065
13095
  name: string()
13066
13096
  }), _void(), {
@@ -19235,6 +19265,34 @@ var KeyEventsForDeviceSchema = object({
19235
19265
  deviceId: number(),
19236
19266
  events: array(KeyEventSchema).readonly()
19237
19267
  });
19268
+ /** One non-empty bucket of the timeline histogram. */
19269
+ var EventDensityBucketSchema = object({
19270
+ bucketStart: number(),
19271
+ motion: number().int(),
19272
+ object: number().int(),
19273
+ audio: number().int()
19274
+ });
19275
+ /**
19276
+ * One camera's row in a `getEventDensityBatch` answer.
19277
+ *
19278
+ * TWO facts, and the timeline draws them differently:
19279
+ *
19280
+ * - `read: 'read'` — the histogram for this camera. `buckets: []` is a real
19281
+ * claim: read, and nothing happened in the window.
19282
+ * - `read: 'unreadable'` — nobody could answer for this camera. `buckets` is
19283
+ * empty and that emptiness means NOTHING; the timeline must render unknown,
19284
+ * not quiet.
19285
+ *
19286
+ * Fanned out per camera the difference was free — one query rejected while the
19287
+ * others resolved — and collapsing to a batch is the exact moment it is lost
19288
+ * silently. Every requested id gets a row: a caller that asked for twelve and
19289
+ * got eleven cannot tell which one is missing, or that any is.
19290
+ */
19291
+ var EventDensityForDeviceSchema = object({
19292
+ deviceId: number(),
19293
+ read: _enum(["read", "unreadable"]),
19294
+ buckets: array(EventDensityBucketSchema).readonly()
19295
+ });
19238
19296
  object({
19239
19297
  trackId: string(),
19240
19298
  className: string(),
@@ -19545,12 +19603,12 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19545
19603
  since: number(),
19546
19604
  until: number(),
19547
19605
  bucketMs: number().int().positive()
19548
- }), array(object({
19549
- bucketStart: number(),
19550
- motion: number().int(),
19551
- object: number().int(),
19552
- audio: number().int()
19553
- })).readonly()), method(object({
19606
+ }), array(EventDensityBucketSchema).readonly()), method(object({
19607
+ deviceIds: array(number()).min(1).max(200),
19608
+ since: number(),
19609
+ until: number(),
19610
+ bucketMs: number().int().positive()
19611
+ }), array(EventDensityForDeviceSchema).readonly()), method(object({
19554
19612
  deviceId: number(),
19555
19613
  cutoffMs: number()
19556
19614
  }), object({
@@ -28723,6 +28781,39 @@ var RecordingDaysSchema = object({
28723
28781
  /** Local-midnight epochs (UTC ms) of days that have ≥1 recorded segment. */
28724
28782
  days: array(number())
28725
28783
  });
28784
+ /**
28785
+ * One camera's row in a `getAvailabilityBatch` answer.
28786
+ *
28787
+ * `ranges` is EXACTLY what `getAvailability` returns for that camera — the
28788
+ * batch collapses the transport, not the work — plus the one thing the singular
28789
+ * method never had to say:
28790
+ *
28791
+ * - `read: 'read'` — answered. `ranges: []` means "read, and this camera has
28792
+ * no footage in the window", which is a real claim.
28793
+ * - `read: 'unreadable'` — the recorder could not answer for this camera (its
28794
+ * calendar rung threw, its location is unmounted). `ranges` is empty and
28795
+ * that emptiness means NOTHING.
28796
+ *
28797
+ * A timeline that renders the second as the first tells an operator there is no
28798
+ * footage when the truth is that nobody looked.
28799
+ */
28800
+ var RecordingAvailabilityForDeviceSchema = object({
28801
+ deviceId: number(),
28802
+ read: _enum(["read", "unreadable"]),
28803
+ ranges: array(RecordingRangeSchema).readonly()
28804
+ });
28805
+ /**
28806
+ * One camera's row in a `getDaysWithRecordingsBatch` answer. Same rule as
28807
+ * {@link RecordingAvailabilityForDeviceSchema}: `days: []` on a `'read'` row is
28808
+ * "no footage in this month", `read: 'unreadable'` is "nobody could look", and
28809
+ * the date-picker's day dots must not spell them the same.
28810
+ */
28811
+ var RecordingDaysForDeviceSchema = object({
28812
+ deviceId: number(),
28813
+ read: _enum(["read", "unreadable"]),
28814
+ /** Local-midnight epochs (UTC ms) of days that have ≥1 recorded segment. */
28815
+ days: array(number()).readonly()
28816
+ });
28726
28817
  var RecordingManifestSchema = object({
28727
28818
  deviceId: number(),
28728
28819
  /** Local filesystem path to the master playlist; null when no recording exists for the requested range. */
@@ -28957,6 +29048,13 @@ method(object({
28957
29048
  }), RecordingAvailabilitySchema, {
28958
29049
  kind: "query",
28959
29050
  auth: "protected"
29051
+ }), method(object({
29052
+ deviceIds: array(number()).min(1).max(200),
29053
+ fromMs: number(),
29054
+ toMs: number()
29055
+ }), array(RecordingAvailabilityForDeviceSchema).readonly(), {
29056
+ kind: "query",
29057
+ auth: "protected"
28960
29058
  }), method(object({
28961
29059
  deviceId: number(),
28962
29060
  fromMs: number(),
@@ -28965,6 +29063,14 @@ method(object({
28965
29063
  }), RecordingDaysSchema, {
28966
29064
  kind: "query",
28967
29065
  auth: "protected"
29066
+ }), method(object({
29067
+ deviceIds: array(number()).min(1).max(200),
29068
+ fromMs: number(),
29069
+ toMs: number(),
29070
+ tzOffsetMinutes: number()
29071
+ }), array(RecordingDaysForDeviceSchema).readonly(), {
29072
+ kind: "query",
29073
+ auth: "protected"
28968
29074
  }), method(object({
28969
29075
  deviceId: number(),
28970
29076
  fromMs: number(),
@@ -34125,6 +34231,12 @@ Object.freeze({
34125
34231
  addonId: null,
34126
34232
  access: "view"
34127
34233
  },
34234
+ "deviceManager.migrateDevice": {
34235
+ capName: "device-manager",
34236
+ capScope: "system",
34237
+ addonId: null,
34238
+ access: "create"
34239
+ },
34128
34240
  "deviceManager.persistConfig": {
34129
34241
  capName: "device-manager",
34130
34242
  capScope: "system",
@@ -35859,6 +35971,12 @@ Object.freeze({
35859
35971
  addonId: null,
35860
35972
  access: "view"
35861
35973
  },
35974
+ "pipelineAnalytics.getEventDensityBatch": {
35975
+ capName: "pipeline-analytics",
35976
+ capScope: "device",
35977
+ addonId: null,
35978
+ access: "view"
35979
+ },
35862
35980
  "pipelineAnalytics.getEventMedia": {
35863
35981
  capName: "pipeline-analytics",
35864
35982
  capScope: "device",
@@ -36981,12 +37099,24 @@ Object.freeze({
36981
37099
  addonId: null,
36982
37100
  access: "view"
36983
37101
  },
37102
+ "recording.getAvailabilityBatch": {
37103
+ capName: "recording",
37104
+ capScope: "system",
37105
+ addonId: null,
37106
+ access: "view"
37107
+ },
36984
37108
  "recording.getDaysWithRecordings": {
36985
37109
  capName: "recording",
36986
37110
  capScope: "system",
36987
37111
  addonId: null,
36988
37112
  access: "view"
36989
37113
  },
37114
+ "recording.getDaysWithRecordingsBatch": {
37115
+ capName: "recording",
37116
+ capScope: "system",
37117
+ addonId: null,
37118
+ access: "view"
37119
+ },
36990
37120
  "recording.getDeviceConfig": {
36991
37121
  capName: "recording",
36992
37122
  capScope: "system",
@@ -39591,6 +39721,11 @@ Object.freeze({
39591
39721
  form: "single",
39592
39722
  optional: false
39593
39723
  }],
39724
+ "pipelineAnalytics.getEventDensityBatch": [{
39725
+ name: "deviceIds",
39726
+ form: "array",
39727
+ optional: false
39728
+ }],
39594
39729
  "pipelineAnalytics.getEventMedia": [{
39595
39730
  name: "deviceId",
39596
39731
  form: "single",
@@ -40006,11 +40141,21 @@ Object.freeze({
40006
40141
  form: "single",
40007
40142
  optional: false
40008
40143
  }],
40144
+ "recording.getAvailabilityBatch": [{
40145
+ name: "deviceIds",
40146
+ form: "array",
40147
+ optional: false
40148
+ }],
40009
40149
  "recording.getDaysWithRecordings": [{
40010
40150
  name: "deviceId",
40011
40151
  form: "single",
40012
40152
  optional: false
40013
40153
  }],
40154
+ "recording.getDaysWithRecordingsBatch": [{
40155
+ name: "deviceIds",
40156
+ form: "array",
40157
+ optional: false
40158
+ }],
40014
40159
  "recording.getDeviceConfig": [{
40015
40160
  name: "deviceId",
40016
40161
  form: "single",
package/dist/addon.mjs CHANGED
@@ -7794,7 +7794,8 @@ var OpsLogReasonSchema = _enum([
7794
7794
  "quota",
7795
7795
  "manual",
7796
7796
  "operator",
7797
- "maintenance"
7797
+ "maintenance",
7798
+ "orphaned-device"
7798
7799
  ]);
7799
7800
  /** One audit row, shared verbatim by both domains. */
7800
7801
  var OpsLogEntrySchema = object({
@@ -13034,10 +13035,39 @@ var DevicePersistConfigPayloadSchema = object({
13034
13035
  deviceId: number(),
13035
13036
  data: record(string(), unknown())
13036
13037
  });
13038
+ /** What a migration actually did, per switch. `unreachable` is a first-class
13039
+ * answer: a camera that could not be asked is not a camera that was silenced. */
13040
+ var MigrateSwitchOutcomeSchema = _enum([
13041
+ "off",
13042
+ "on",
13043
+ "not-offered",
13044
+ "unreachable"
13045
+ ]);
13046
+ var MigrateSwitchReportSchema = object({
13047
+ deviceId: number(),
13048
+ switchId: CameraSwitchIdSchema,
13049
+ outcome: MigrateSwitchOutcomeSchema,
13050
+ detail: string().optional()
13051
+ });
13052
+ var MigrateDeviceResultSchema = object({
13053
+ sourceId: number(),
13054
+ targetId: number(),
13055
+ switches: array(MigrateSwitchReportSchema).readonly(),
13056
+ /** Switches that could NOT be turned off on the source. Empty is the only
13057
+ * value meaning the replaced hardware is quiet. */
13058
+ sourceStillLive: array(CameraSwitchIdSchema).readonly(),
13059
+ swapped: boolean()
13060
+ });
13037
13061
  method(object({
13038
13062
  addonId: string(),
13039
13063
  stableId: string()
13040
- }), 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({
13064
+ }), object({ id: number() }), { kind: "mutation" }), method(object({
13065
+ sourceId: number(),
13066
+ targetId: number()
13067
+ }), MigrateDeviceResultSchema, {
13068
+ kind: "mutation",
13069
+ auth: "admin"
13070
+ }), 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({
13041
13071
  deviceId: number(),
13042
13072
  name: string()
13043
13073
  }), _void(), {
@@ -19212,6 +19242,34 @@ var KeyEventsForDeviceSchema = object({
19212
19242
  deviceId: number(),
19213
19243
  events: array(KeyEventSchema).readonly()
19214
19244
  });
19245
+ /** One non-empty bucket of the timeline histogram. */
19246
+ var EventDensityBucketSchema = object({
19247
+ bucketStart: number(),
19248
+ motion: number().int(),
19249
+ object: number().int(),
19250
+ audio: number().int()
19251
+ });
19252
+ /**
19253
+ * One camera's row in a `getEventDensityBatch` answer.
19254
+ *
19255
+ * TWO facts, and the timeline draws them differently:
19256
+ *
19257
+ * - `read: 'read'` — the histogram for this camera. `buckets: []` is a real
19258
+ * claim: read, and nothing happened in the window.
19259
+ * - `read: 'unreadable'` — nobody could answer for this camera. `buckets` is
19260
+ * empty and that emptiness means NOTHING; the timeline must render unknown,
19261
+ * not quiet.
19262
+ *
19263
+ * Fanned out per camera the difference was free — one query rejected while the
19264
+ * others resolved — and collapsing to a batch is the exact moment it is lost
19265
+ * silently. Every requested id gets a row: a caller that asked for twelve and
19266
+ * got eleven cannot tell which one is missing, or that any is.
19267
+ */
19268
+ var EventDensityForDeviceSchema = object({
19269
+ deviceId: number(),
19270
+ read: _enum(["read", "unreadable"]),
19271
+ buckets: array(EventDensityBucketSchema).readonly()
19272
+ });
19215
19273
  object({
19216
19274
  trackId: string(),
19217
19275
  className: string(),
@@ -19522,12 +19580,12 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19522
19580
  since: number(),
19523
19581
  until: number(),
19524
19582
  bucketMs: number().int().positive()
19525
- }), array(object({
19526
- bucketStart: number(),
19527
- motion: number().int(),
19528
- object: number().int(),
19529
- audio: number().int()
19530
- })).readonly()), method(object({
19583
+ }), array(EventDensityBucketSchema).readonly()), method(object({
19584
+ deviceIds: array(number()).min(1).max(200),
19585
+ since: number(),
19586
+ until: number(),
19587
+ bucketMs: number().int().positive()
19588
+ }), array(EventDensityForDeviceSchema).readonly()), method(object({
19531
19589
  deviceId: number(),
19532
19590
  cutoffMs: number()
19533
19591
  }), object({
@@ -28700,6 +28758,39 @@ var RecordingDaysSchema = object({
28700
28758
  /** Local-midnight epochs (UTC ms) of days that have ≥1 recorded segment. */
28701
28759
  days: array(number())
28702
28760
  });
28761
+ /**
28762
+ * One camera's row in a `getAvailabilityBatch` answer.
28763
+ *
28764
+ * `ranges` is EXACTLY what `getAvailability` returns for that camera — the
28765
+ * batch collapses the transport, not the work — plus the one thing the singular
28766
+ * method never had to say:
28767
+ *
28768
+ * - `read: 'read'` — answered. `ranges: []` means "read, and this camera has
28769
+ * no footage in the window", which is a real claim.
28770
+ * - `read: 'unreadable'` — the recorder could not answer for this camera (its
28771
+ * calendar rung threw, its location is unmounted). `ranges` is empty and
28772
+ * that emptiness means NOTHING.
28773
+ *
28774
+ * A timeline that renders the second as the first tells an operator there is no
28775
+ * footage when the truth is that nobody looked.
28776
+ */
28777
+ var RecordingAvailabilityForDeviceSchema = object({
28778
+ deviceId: number(),
28779
+ read: _enum(["read", "unreadable"]),
28780
+ ranges: array(RecordingRangeSchema).readonly()
28781
+ });
28782
+ /**
28783
+ * One camera's row in a `getDaysWithRecordingsBatch` answer. Same rule as
28784
+ * {@link RecordingAvailabilityForDeviceSchema}: `days: []` on a `'read'` row is
28785
+ * "no footage in this month", `read: 'unreadable'` is "nobody could look", and
28786
+ * the date-picker's day dots must not spell them the same.
28787
+ */
28788
+ var RecordingDaysForDeviceSchema = object({
28789
+ deviceId: number(),
28790
+ read: _enum(["read", "unreadable"]),
28791
+ /** Local-midnight epochs (UTC ms) of days that have ≥1 recorded segment. */
28792
+ days: array(number()).readonly()
28793
+ });
28703
28794
  var RecordingManifestSchema = object({
28704
28795
  deviceId: number(),
28705
28796
  /** Local filesystem path to the master playlist; null when no recording exists for the requested range. */
@@ -28934,6 +29025,13 @@ method(object({
28934
29025
  }), RecordingAvailabilitySchema, {
28935
29026
  kind: "query",
28936
29027
  auth: "protected"
29028
+ }), method(object({
29029
+ deviceIds: array(number()).min(1).max(200),
29030
+ fromMs: number(),
29031
+ toMs: number()
29032
+ }), array(RecordingAvailabilityForDeviceSchema).readonly(), {
29033
+ kind: "query",
29034
+ auth: "protected"
28937
29035
  }), method(object({
28938
29036
  deviceId: number(),
28939
29037
  fromMs: number(),
@@ -28942,6 +29040,14 @@ method(object({
28942
29040
  }), RecordingDaysSchema, {
28943
29041
  kind: "query",
28944
29042
  auth: "protected"
29043
+ }), method(object({
29044
+ deviceIds: array(number()).min(1).max(200),
29045
+ fromMs: number(),
29046
+ toMs: number(),
29047
+ tzOffsetMinutes: number()
29048
+ }), array(RecordingDaysForDeviceSchema).readonly(), {
29049
+ kind: "query",
29050
+ auth: "protected"
28945
29051
  }), method(object({
28946
29052
  deviceId: number(),
28947
29053
  fromMs: number(),
@@ -34102,6 +34208,12 @@ Object.freeze({
34102
34208
  addonId: null,
34103
34209
  access: "view"
34104
34210
  },
34211
+ "deviceManager.migrateDevice": {
34212
+ capName: "device-manager",
34213
+ capScope: "system",
34214
+ addonId: null,
34215
+ access: "create"
34216
+ },
34105
34217
  "deviceManager.persistConfig": {
34106
34218
  capName: "device-manager",
34107
34219
  capScope: "system",
@@ -35836,6 +35948,12 @@ Object.freeze({
35836
35948
  addonId: null,
35837
35949
  access: "view"
35838
35950
  },
35951
+ "pipelineAnalytics.getEventDensityBatch": {
35952
+ capName: "pipeline-analytics",
35953
+ capScope: "device",
35954
+ addonId: null,
35955
+ access: "view"
35956
+ },
35839
35957
  "pipelineAnalytics.getEventMedia": {
35840
35958
  capName: "pipeline-analytics",
35841
35959
  capScope: "device",
@@ -36958,12 +37076,24 @@ Object.freeze({
36958
37076
  addonId: null,
36959
37077
  access: "view"
36960
37078
  },
37079
+ "recording.getAvailabilityBatch": {
37080
+ capName: "recording",
37081
+ capScope: "system",
37082
+ addonId: null,
37083
+ access: "view"
37084
+ },
36961
37085
  "recording.getDaysWithRecordings": {
36962
37086
  capName: "recording",
36963
37087
  capScope: "system",
36964
37088
  addonId: null,
36965
37089
  access: "view"
36966
37090
  },
37091
+ "recording.getDaysWithRecordingsBatch": {
37092
+ capName: "recording",
37093
+ capScope: "system",
37094
+ addonId: null,
37095
+ access: "view"
37096
+ },
36967
37097
  "recording.getDeviceConfig": {
36968
37098
  capName: "recording",
36969
37099
  capScope: "system",
@@ -39568,6 +39698,11 @@ Object.freeze({
39568
39698
  form: "single",
39569
39699
  optional: false
39570
39700
  }],
39701
+ "pipelineAnalytics.getEventDensityBatch": [{
39702
+ name: "deviceIds",
39703
+ form: "array",
39704
+ optional: false
39705
+ }],
39571
39706
  "pipelineAnalytics.getEventMedia": [{
39572
39707
  name: "deviceId",
39573
39708
  form: "single",
@@ -39983,11 +40118,21 @@ Object.freeze({
39983
40118
  form: "single",
39984
40119
  optional: false
39985
40120
  }],
40121
+ "recording.getAvailabilityBatch": [{
40122
+ name: "deviceIds",
40123
+ form: "array",
40124
+ optional: false
40125
+ }],
39986
40126
  "recording.getDaysWithRecordings": [{
39987
40127
  name: "deviceId",
39988
40128
  form: "single",
39989
40129
  optional: false
39990
40130
  }],
40131
+ "recording.getDaysWithRecordingsBatch": [{
40132
+ name: "deviceIds",
40133
+ form: "array",
40134
+ optional: false
40135
+ }],
39991
40136
  "recording.getDeviceConfig": [{
39992
40137
  name: "deviceId",
39993
40138
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-terminal",
3
- "version": "0.1.56",
3
+ "version": "0.1.58",
4
4
  "description": "Interactive terminal sessions (pty + xterm) as a CamStack addon",
5
5
  "keywords": [
6
6
  "camstack",