een-api-toolkit 0.3.20 → 0.3.28

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.
Files changed (36) hide show
  1. package/.claude/agents/docs-accuracy-reviewer.md +146 -0
  2. package/.claude/agents/een-auth-agent.md +168 -0
  3. package/.claude/agents/een-devices-agent.md +331 -0
  4. package/.claude/agents/een-events-agent.md +375 -0
  5. package/.claude/agents/een-media-agent.md +315 -0
  6. package/.claude/agents/een-setup-agent.md +126 -0
  7. package/.claude/agents/een-users-agent.md +239 -0
  8. package/.claude/agents/test-runner.md +144 -0
  9. package/CHANGELOG.md +10 -45
  10. package/README.md +23 -0
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.js.map +1 -1
  13. package/docs/AI-CONTEXT.md +169 -1700
  14. package/docs/ai-reference/AI-AUTH.md +288 -0
  15. package/docs/ai-reference/AI-DEVICES.md +569 -0
  16. package/docs/ai-reference/AI-EVENTS.md +1745 -0
  17. package/docs/ai-reference/AI-MEDIA.md +974 -0
  18. package/docs/ai-reference/AI-SETUP.md +267 -0
  19. package/docs/ai-reference/AI-USERS.md +255 -0
  20. package/examples/vue-event-subscriptions/package-lock.json +8 -1
  21. package/examples/vue-event-subscriptions/package.json +1 -0
  22. package/examples/vue-event-subscriptions/src/App.vue +1 -41
  23. package/examples/vue-event-subscriptions/src/composables/useHlsPlayer.ts +272 -0
  24. package/examples/vue-event-subscriptions/src/main.ts +3 -3
  25. package/examples/vue-event-subscriptions/src/stores/connection.ts +101 -0
  26. package/examples/vue-event-subscriptions/src/stores/mediaSession.ts +79 -0
  27. package/examples/vue-event-subscriptions/src/views/LiveEvents.vue +349 -88
  28. package/examples/vue-event-subscriptions/src/views/Logout.vue +6 -0
  29. package/examples/vue-event-subscriptions/src/views/Subscriptions.vue +0 -13
  30. package/examples/vue-events/package-lock.json +8 -1
  31. package/examples/vue-events/package.json +1 -0
  32. package/examples/vue-events/src/components/EventsModal.vue +269 -47
  33. package/examples/vue-events/src/composables/useHlsPlayer.ts +272 -0
  34. package/examples/vue-events/src/stores/mediaSession.ts +79 -0
  35. package/package.json +10 -2
  36. package/scripts/setup-agents.ts +116 -0
@@ -0,0 +1,569 @@
1
+ # Cameras & Bridges API - EEN API Toolkit
2
+
3
+ > **Version:** 0.3.28
4
+ >
5
+ > Complete reference for camera and bridge management.
6
+ > Load this document when working with devices.
7
+
8
+ ---
9
+
10
+ ## Camera Types
11
+
12
+ ### Camera
13
+
14
+ ```typescript
15
+ type CameraStatus =
16
+ | 'online' | 'offline' | 'deviceOffline' | 'bridgeOffline'
17
+ | 'invalidCredentials' | 'error' | 'streaming' | 'registered'
18
+ | 'attaching' | 'initializing'
19
+
20
+ interface Camera {
21
+ id: string
22
+ name: string
23
+ accountId: string
24
+ bridgeId?: string | null
25
+ locationId?: string | null
26
+ status?: CameraStatus
27
+ timezone?: string
28
+ guid?: string
29
+ ipAddress?: string
30
+ macAddress?: string
31
+ tags?: string[]
32
+ notes?: string
33
+ deviceInfo?: CameraDeviceInfo
34
+ shareDetails?: CameraShareDetails
35
+ devicePosition?: CameraDevicePosition
36
+ createdAt?: string
37
+ updatedAt?: string
38
+ }
39
+
40
+ interface CameraDeviceInfo {
41
+ make?: string // Manufacturer (e.g., "Axis")
42
+ model?: string // Model name
43
+ firmwareVersion?: string
44
+ directToCloud?: boolean // No bridge required
45
+ serialNumber?: string
46
+ resolution?: string
47
+ }
48
+ ```
49
+
50
+ ### Parameters
51
+
52
+ ```typescript
53
+ interface ListCamerasParams {
54
+ pageSize?: number // Results per page
55
+ pageToken?: string // Pagination token
56
+ include?: string[] // Additional fields
57
+ sort?: string[] // Sort order
58
+ status__in?: CameraStatus[] // Filter by status
59
+ status__ne?: CameraStatus // Exclude by status
60
+ tags__contains?: string[] // All tags must match
61
+ tags__any?: string[] // Any tag matches
62
+ name__contains?: string // Partial name match
63
+ q?: string // Full-text search
64
+ bridgeId__in?: string[] // Filter by bridge
65
+ locationId__in?: string[] // Filter by location
66
+ }
67
+ ```
68
+
69
+ ---
70
+
71
+ ## Bridge Types
72
+
73
+ ### Bridge
74
+
75
+ ```typescript
76
+ type BridgeStatus =
77
+ | 'online' | 'offline' | 'error' | 'idle'
78
+ | 'registered' | 'attaching' | 'initializing'
79
+
80
+ interface Bridge {
81
+ id: string
82
+ name: string
83
+ accountId: string
84
+ locationId?: string | null
85
+ guid?: string
86
+ timezone?: string
87
+ status?: BridgeStatus | { connectionStatus?: BridgeStatus }
88
+ tags?: string[]
89
+ deviceInfo?: BridgeDeviceInfo
90
+ networkInfo?: BridgeNetworkInfo
91
+ cameraCount?: number
92
+ createdAt?: string
93
+ updatedAt?: string
94
+ }
95
+
96
+ interface BridgeNetworkInfo {
97
+ localIpAddress?: string
98
+ publicIpAddress?: string
99
+ macAddress?: string
100
+ }
101
+ ```
102
+
103
+ ---
104
+
105
+ ## Camera Functions
106
+
107
+ ### getCameras(params?)
108
+
109
+ ```typescript
110
+ import { getCameras } from 'een-api-toolkit'
111
+
112
+ // Basic usage
113
+ const { data, error } = await getCameras()
114
+
115
+ // Filter by status
116
+ const { data } = await getCameras({
117
+ status__in: ['online', 'streaming']
118
+ })
119
+
120
+ // Full-text search
121
+ const { data } = await getCameras({
122
+ q: 'front door',
123
+ include: ['deviceInfo', 'status']
124
+ })
125
+
126
+ // Filter by tags
127
+ const { data } = await getCameras({
128
+ tags__any: ['floor1', 'floor2']
129
+ })
130
+ ```
131
+
132
+ ### getCamera(cameraId, params?)
133
+
134
+ ```typescript
135
+ import { getCamera } from 'een-api-toolkit'
136
+
137
+ const { data, error } = await getCamera('camera-id-123')
138
+
139
+ // With additional fields
140
+ const { data: detailed } = await getCamera('camera-id-123', {
141
+ include: ['deviceInfo', 'status', 'shareDetails', 'tags']
142
+ })
143
+ ```
144
+
145
+ ---
146
+
147
+ ## Bridge Functions
148
+
149
+ ### getBridges(params?)
150
+
151
+ ```typescript
152
+ import { getBridges } from 'een-api-toolkit'
153
+
154
+ // Basic usage
155
+ const { data, error } = await getBridges()
156
+
157
+ // Filter by status
158
+ const { data } = await getBridges({
159
+ status__in: ['online'],
160
+ include: ['deviceInfo', 'networkInfo']
161
+ })
162
+ ```
163
+
164
+ ### getBridge(bridgeId, params?)
165
+
166
+ ```typescript
167
+ import { getBridge } from 'een-api-toolkit'
168
+
169
+ const { data, error } = await getBridge('bridge-id-123', {
170
+ include: ['deviceInfo', 'networkInfo', 'status']
171
+ })
172
+ ```
173
+
174
+ ---
175
+
176
+ ## Filter Patterns
177
+
178
+ | Filter | Example | Description |
179
+ |--------|---------|-------------|
180
+ | `status__in` | `['online', 'streaming']` | Include specific statuses |
181
+ | `status__ne` | `'offline'` | Exclude a status |
182
+ | `tags__contains` | `['outdoor']` | All tags must match |
183
+ | `tags__any` | `['floor1', 'floor2']` | Any tag matches |
184
+ | `name__contains` | `'lobby'` | Partial name match |
185
+ | `q` | `'front door'` | Full-text search |
186
+
187
+ ---
188
+
189
+ ## Vue Components
190
+
191
+ ### Cameras.vue
192
+
193
+ ```vue
194
+ <script setup lang="ts">
195
+ import { ref, computed, watch, onMounted } from 'vue'
196
+ import { getCameras, type Camera, type CameraStatus, type EenError, type ListCamerasParams } from 'een-api-toolkit'
197
+
198
+ // Status filter
199
+ const statusFilter = ref<CameraStatus | ''>('')
200
+
201
+ // Reactive state
202
+ const cameras = ref<Camera[]>([])
203
+ const loading = ref(false)
204
+ const error = ref<EenError | null>(null)
205
+ const nextPageToken = ref<string | undefined>(undefined)
206
+ const totalSize = ref<number | undefined>(undefined)
207
+
208
+ const hasNextPage = computed(() => !!nextPageToken.value)
209
+
210
+ const params = ref<ListCamerasParams>({
211
+ pageSize: 20,
212
+ include: ['deviceInfo', 'status']
213
+ })
214
+
215
+ async function fetchCameras(fetchParams?: ListCamerasParams, append = false) {
216
+ loading.value = true
217
+ error.value = null
218
+
219
+ const mergedParams = { ...params.value, ...fetchParams }
220
+ const result = await getCameras(mergedParams)
221
+
222
+ if (result.error) {
223
+ error.value = result.error
224
+ if (!append) {
225
+ cameras.value = []
226
+ totalSize.value = undefined
227
+ }
228
+ nextPageToken.value = undefined
229
+ } else {
230
+ if (append) {
231
+ cameras.value = [...cameras.value, ...result.data.results]
232
+ } else {
233
+ cameras.value = result.data.results
234
+ }
235
+ nextPageToken.value = result.data.nextPageToken
236
+ totalSize.value = result.data.totalSize
237
+ }
238
+
239
+ loading.value = false
240
+ return result
241
+ }
242
+
243
+ function refresh() {
244
+ return fetchCameras()
245
+ }
246
+
247
+ async function fetchNextPage() {
248
+ if (!nextPageToken.value) return
249
+ return fetchCameras({ ...params.value, pageToken: nextPageToken.value }, true)
250
+ }
251
+
252
+ function setParams(newParams: ListCamerasParams) {
253
+ params.value = newParams
254
+ }
255
+
256
+ // Watch for status filter changes
257
+ watch(statusFilter, async (newStatus) => {
258
+ if (newStatus) {
259
+ setParams({
260
+ pageSize: 20,
261
+ include: ['deviceInfo', 'status'],
262
+ status__in: [newStatus]
263
+ })
264
+ } else {
265
+ setParams({
266
+ pageSize: 20,
267
+ include: ['deviceInfo', 'status']
268
+ })
269
+ }
270
+ await fetchCameras()
271
+ })
272
+
273
+ // Helper to extract status string from the union type
274
+ function getStatusString(status?: CameraStatus | { connectionStatus?: CameraStatus }): CameraStatus | undefined {
275
+ if (!status) return undefined
276
+ if (typeof status === 'string') return status
277
+ return status.connectionStatus
278
+ }
279
+
280
+ // Get status badge class
281
+ function getStatusClass(status?: CameraStatus | { connectionStatus?: CameraStatus }): string {
282
+ const statusStr = getStatusString(status)
283
+ switch (statusStr) {
284
+ case 'online':
285
+ case 'streaming':
286
+ return 'status-online'
287
+ case 'offline':
288
+ case 'deviceOffline':
289
+ case 'bridgeOffline':
290
+ return 'status-offline'
291
+ case 'error':
292
+ case 'invalidCredentials':
293
+ return 'status-error'
294
+ default:
295
+ return 'status-unknown'
296
+ }
297
+ }
298
+
299
+ onMounted(() => {
300
+ fetchCameras()
301
+ })
302
+ </script>
303
+
304
+ <template>
305
+ <div class="cameras">
306
+ <div class="header">
307
+ <h2>Cameras</h2>
308
+ <div class="controls">
309
+ <select v-model="statusFilter" class="status-filter">
310
+ <option value="">All Statuses</option>
311
+ <option value="online">Online</option>
312
+ <option value="streaming">Streaming</option>
313
+ <option value="offline">Offline</option>
314
+ <option value="deviceOffline">Device Offline</option>
315
+ <option value="bridgeOffline">Bridge Offline</option>
316
+ <option value="error">Error</option>
317
+ </select>
318
+ <button @click="refresh" :disabled="loading">
319
+ {{ loading ? 'Loading...' : 'Refresh' }}
320
+ </button>
321
+ </div>
322
+ </div>
323
+
324
+ <div v-if="totalSize !== undefined" class="total-count">
325
+ Total: {{ totalSize }} camera{{ totalSize !== 1 ? 's' : '' }}
326
+ </div>
327
+
328
+ <div v-if="loading && cameras.length === 0" class="loading">
329
+ Loading cameras...
330
+ </div>
331
+
332
+ <div v-else-if="error" class="error">
333
+ Error: {{ error.message }}
334
+ </div>
335
+
336
+ <div v-else>
337
+ <div v-if="cameras.length > 0" class="camera-grid">
338
+ <router-link
339
+ v-for="camera in cameras"
340
+ :key="camera.id"
341
+ :to="`/cameras/${camera.id}`"
342
+ class="camera-card"
343
+ >
344
+ <div class="camera-header">
345
+ <h3>{{ camera.name }}</h3>
346
+ <span :class="['status-badge', getStatusClass(camera.status)]">
347
+ {{ getStatusString(camera.status) || 'Unknown' }}
348
+ </span>
349
+ </div>
350
+ <div class="camera-details">
351
+ <p v-if="camera.deviceInfo?.make || camera.deviceInfo?.model">
352
+ <strong>Device:</strong>
353
+ {{ camera.deviceInfo?.make || '' }} {{ camera.deviceInfo?.model || '' }}
354
+ </p>
355
+ <p v-if="camera.locationId">
356
+ <strong>Location:</strong> {{ camera.locationId }}
357
+ </p>
358
+ <p v-if="camera.tags && camera.tags.length > 0">
359
+ <strong>Tags:</strong> {{ camera.tags.join(', ') }}
360
+ </p>
361
+ </div>
362
+ </router-link>
363
+ </div>
364
+
365
+ <p v-else class="no-cameras">
366
+ No cameras found{{ statusFilter ? ' with selected filter' : '' }}.
367
+ </p>
368
+
369
+ <div v-if="hasNextPage" class="pagination">
370
+ <button @click="fetchNextPage" :disabled="loading">
371
+ {{ loading ? 'Loading...' : 'Load More' }}
372
+ </button>
373
+ </div>
374
+ </div>
375
+ </div>
376
+ </template>
377
+ ```
378
+
379
+ ### Bridges.vue
380
+
381
+ ```vue
382
+ <script setup lang="ts">
383
+ import { ref, computed, watch, onMounted } from 'vue'
384
+ import { getBridges, type Bridge, type BridgeStatus, type EenError, type ListBridgesParams } from 'een-api-toolkit'
385
+
386
+ // Status filter
387
+ const statusFilter = ref<BridgeStatus | ''>('')
388
+
389
+ // Reactive state
390
+ const bridges = ref<Bridge[]>([])
391
+ const loading = ref(false)
392
+ const error = ref<EenError | null>(null)
393
+ const nextPageToken = ref<string | undefined>(undefined)
394
+ const totalSize = ref<number | undefined>(undefined)
395
+
396
+ const hasNextPage = computed(() => !!nextPageToken.value)
397
+
398
+ const params = ref<ListBridgesParams>({
399
+ pageSize: 20,
400
+ include: ['deviceInfo', 'status', 'networkInfo']
401
+ })
402
+
403
+ async function fetchBridges(fetchParams?: ListBridgesParams, append = false) {
404
+ loading.value = true
405
+ error.value = null
406
+
407
+ const mergedParams = { ...params.value, ...fetchParams }
408
+ const result = await getBridges(mergedParams)
409
+
410
+ if (result.error) {
411
+ error.value = result.error
412
+ if (!append) {
413
+ bridges.value = []
414
+ totalSize.value = undefined
415
+ }
416
+ nextPageToken.value = undefined
417
+ } else {
418
+ if (append) {
419
+ bridges.value = [...bridges.value, ...result.data.results]
420
+ } else {
421
+ bridges.value = result.data.results
422
+ }
423
+ nextPageToken.value = result.data.nextPageToken
424
+ totalSize.value = result.data.totalSize
425
+ }
426
+
427
+ loading.value = false
428
+ return result
429
+ }
430
+
431
+ function refresh() {
432
+ return fetchBridges()
433
+ }
434
+
435
+ async function fetchNextPage() {
436
+ if (!nextPageToken.value) return
437
+ return fetchBridges({ ...params.value, pageToken: nextPageToken.value }, true)
438
+ }
439
+
440
+ function setParams(newParams: ListBridgesParams) {
441
+ params.value = newParams
442
+ }
443
+
444
+ // Watch for status filter changes
445
+ watch(statusFilter, async (newStatus) => {
446
+ if (newStatus) {
447
+ setParams({
448
+ pageSize: 20,
449
+ include: ['deviceInfo', 'status', 'networkInfo'],
450
+ status__in: [newStatus]
451
+ })
452
+ } else {
453
+ setParams({
454
+ pageSize: 20,
455
+ include: ['deviceInfo', 'status', 'networkInfo']
456
+ })
457
+ }
458
+ await fetchBridges()
459
+ })
460
+
461
+ // Helper to extract status string from the union type
462
+ function getStatusString(status?: BridgeStatus | { connectionStatus?: BridgeStatus }): BridgeStatus | undefined {
463
+ if (!status) return undefined
464
+ if (typeof status === 'string') return status
465
+ return status.connectionStatus
466
+ }
467
+
468
+ // Get status badge class
469
+ function getStatusClass(status?: BridgeStatus | { connectionStatus?: BridgeStatus }): string {
470
+ const statusStr = getStatusString(status)
471
+ switch (statusStr) {
472
+ case 'online':
473
+ return 'status-online'
474
+ case 'offline':
475
+ return 'status-offline'
476
+ case 'error':
477
+ return 'status-error'
478
+ default:
479
+ return 'status-unknown'
480
+ }
481
+ }
482
+
483
+ onMounted(() => {
484
+ fetchBridges()
485
+ })
486
+ </script>
487
+
488
+ <template>
489
+ <div class="bridges">
490
+ <div class="header">
491
+ <h2>Bridges</h2>
492
+ <div class="controls">
493
+ <select v-model="statusFilter" class="status-filter">
494
+ <option value="">All Statuses</option>
495
+ <option value="online">Online</option>
496
+ <option value="offline">Offline</option>
497
+ <option value="error">Error</option>
498
+ <option value="idle">Idle</option>
499
+ </select>
500
+ <button @click="refresh" :disabled="loading">
501
+ {{ loading ? 'Loading...' : 'Refresh' }}
502
+ </button>
503
+ </div>
504
+ </div>
505
+
506
+ <div v-if="totalSize !== undefined" class="total-count">
507
+ Total: {{ totalSize }} bridge{{ totalSize !== 1 ? 's' : '' }}
508
+ </div>
509
+
510
+ <div v-if="loading && bridges.length === 0" class="loading">
511
+ Loading bridges...
512
+ </div>
513
+
514
+ <div v-else-if="error" class="error">
515
+ Error: {{ error.message }}
516
+ </div>
517
+
518
+ <div v-else>
519
+ <div v-if="bridges.length > 0" class="bridge-grid">
520
+ <router-link
521
+ v-for="bridge in bridges"
522
+ :key="bridge.id"
523
+ :to="`/bridges/${bridge.id}`"
524
+ class="bridge-card"
525
+ >
526
+ <div class="bridge-header">
527
+ <h3>{{ bridge.name }}</h3>
528
+ <span :class="['status-badge', getStatusClass(bridge.status)]">
529
+ {{ getStatusString(bridge.status) || 'Unknown' }}
530
+ </span>
531
+ </div>
532
+ <div class="bridge-details">
533
+ <p v-if="bridge.deviceInfo?.make || bridge.deviceInfo?.model">
534
+ <strong>Device:</strong>
535
+ {{ bridge.deviceInfo?.make || '' }} {{ bridge.deviceInfo?.model || '' }}
536
+ </p>
537
+ <p v-if="bridge.networkInfo?.localIpAddress">
538
+ <strong>IP:</strong> {{ bridge.networkInfo.localIpAddress }}
539
+ </p>
540
+ <p v-if="bridge.locationId">
541
+ <strong>Location:</strong> {{ bridge.locationId }}
542
+ </p>
543
+ <p v-if="bridge.tags && bridge.tags.length > 0">
544
+ <strong>Tags:</strong> {{ bridge.tags.join(', ') }}
545
+ </p>
546
+ </div>
547
+ </router-link>
548
+ </div>
549
+
550
+ <p v-else class="no-bridges">
551
+ No bridges found{{ statusFilter ? ' with selected filter' : '' }}.
552
+ </p>
553
+
554
+ <div v-if="hasNextPage" class="pagination">
555
+ <button @click="fetchNextPage" :disabled="loading">
556
+ {{ loading ? 'Loading...' : 'Load More' }}
557
+ </button>
558
+ </div>
559
+ </div>
560
+ </div>
561
+ </template>
562
+ ```
563
+
564
+ ---
565
+
566
+ ## Reference Examples
567
+
568
+ - `examples/vue-cameras/`
569
+ - `examples/vue-bridges/`