een-api-toolkit 0.3.30 → 0.3.35

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 (68) hide show
  1. package/.claude/agents/docs-accuracy-reviewer.md +15 -3
  2. package/.claude/agents/een-auth-agent.md +131 -0
  3. package/.claude/agents/een-devices-agent.md +10 -7
  4. package/.claude/agents/een-events-agent.md +98 -0
  5. package/.claude/agents/een-grouping-agent.md +394 -0
  6. package/.claude/agents/een-media-agent.md +25 -5
  7. package/CHANGELOG.md +101 -6
  8. package/README.md +5 -3
  9. package/dist/index.cjs +3 -3
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.ts +561 -0
  12. package/dist/index.js +388 -218
  13. package/dist/index.js.map +1 -1
  14. package/docs/AI-CONTEXT.md +13 -1
  15. package/docs/ai-reference/AI-AUTH.md +1 -1
  16. package/docs/ai-reference/AI-DEVICES.md +1 -1
  17. package/docs/ai-reference/AI-EVENTS.md +1 -1
  18. package/docs/ai-reference/AI-GROUPING.md +411 -0
  19. package/docs/ai-reference/AI-MEDIA.md +1 -1
  20. package/docs/ai-reference/AI-SETUP.md +1 -1
  21. package/docs/ai-reference/AI-USERS.md +1 -1
  22. package/examples/vue-alerts-metrics/README.md +2 -0
  23. package/examples/vue-alerts-metrics/alert-metrics-screenshot.png +0 -0
  24. package/examples/vue-alerts-metrics/e2e/auth.spec.ts +1 -1
  25. package/examples/vue-alerts-metrics/package-lock.json +17 -14
  26. package/examples/vue-alerts-metrics/package.json +1 -1
  27. package/examples/vue-bridges/package-lock.json +21 -15
  28. package/examples/vue-bridges/package.json +1 -1
  29. package/examples/vue-cameras/package-lock.json +21 -15
  30. package/examples/vue-cameras/package.json +1 -1
  31. package/examples/vue-event-subscriptions/README.md +2 -0
  32. package/examples/vue-event-subscriptions/event-subscriptions-screenshot.png +0 -0
  33. package/examples/vue-event-subscriptions/package-lock.json +17 -14
  34. package/examples/vue-event-subscriptions/package.json +1 -1
  35. package/examples/vue-events/events-screenshot.png +0 -0
  36. package/examples/vue-events/package-lock.json +17 -14
  37. package/examples/vue-events/package.json +1 -1
  38. package/examples/vue-feeds/package-lock.json +21 -15
  39. package/examples/vue-feeds/package.json +1 -1
  40. package/examples/vue-layouts/.env.example +12 -0
  41. package/examples/vue-layouts/README.md +320 -0
  42. package/examples/vue-layouts/e2e/app.spec.ts +76 -0
  43. package/examples/vue-layouts/e2e/auth.spec.ts +264 -0
  44. package/examples/vue-layouts/index.html +13 -0
  45. package/examples/vue-layouts/layouts-screenshot.png +0 -0
  46. package/examples/vue-layouts/package-lock.json +1722 -0
  47. package/examples/vue-layouts/package.json +28 -0
  48. package/examples/vue-layouts/playwright.config.ts +47 -0
  49. package/examples/vue-layouts/src/App.vue +124 -0
  50. package/examples/vue-layouts/src/components/LayoutModal.vue +456 -0
  51. package/examples/vue-layouts/src/main.ts +25 -0
  52. package/examples/vue-layouts/src/router/index.ts +62 -0
  53. package/examples/vue-layouts/src/views/Callback.vue +76 -0
  54. package/examples/vue-layouts/src/views/Home.vue +188 -0
  55. package/examples/vue-layouts/src/views/Layouts.vue +355 -0
  56. package/examples/vue-layouts/src/views/Login.vue +33 -0
  57. package/examples/vue-layouts/src/views/Logout.vue +59 -0
  58. package/examples/vue-layouts/src/vite-env.d.ts +12 -0
  59. package/examples/vue-layouts/tsconfig.json +21 -0
  60. package/examples/vue-layouts/tsconfig.node.json +10 -0
  61. package/examples/vue-layouts/vite.config.ts +12 -0
  62. package/examples/vue-media/media-screenshot.png +0 -0
  63. package/examples/vue-media/package-lock.json +19 -14
  64. package/examples/vue-media/package.json +1 -1
  65. package/examples/vue-users/package-lock.json +21 -16
  66. package/examples/vue-users/package.json +2 -2
  67. package/package.json +2 -2
  68. package/scripts/setup-agents.ts +0 -0
@@ -0,0 +1,394 @@
1
+ ---
2
+ name: een-grouping-agent
3
+ description: |
4
+ Use this agent when working with layouts (camera groupings): listing layouts,
5
+ creating/editing layouts, managing panes, or implementing layout selection UI
6
+ with the een-api-toolkit.
7
+ model: inherit
8
+ color: purple
9
+ ---
10
+
11
+ You are an expert in layout (camera grouping) management with the een-api-toolkit.
12
+
13
+ ## Examples
14
+
15
+ <example>
16
+ Context: User wants to display a layout list.
17
+ user: "How do I show all layouts in a grid?"
18
+ assistant: "I'll use the een-grouping-agent to help implement layout listing using getLayouts()."
19
+ <Task tool call to launch een-grouping-agent>
20
+ </example>
21
+
22
+ <example>
23
+ Context: User wants to create a new layout.
24
+ user: "How do I create a layout with 3 cameras?"
25
+ assistant: "I'll use the een-grouping-agent to implement layout creation with createLayout()."
26
+ <Task tool call to launch een-grouping-agent>
27
+ </example>
28
+
29
+ <example>
30
+ Context: User wants to edit layout settings.
31
+ user: "How do I change the number of columns in a layout?"
32
+ assistant: "I'll use the een-grouping-agent to help update layout settings with updateLayout()."
33
+ <Task tool call to launch een-grouping-agent>
34
+ </example>
35
+
36
+ ## Context Files
37
+ - docs/AI-CONTEXT.md (overview)
38
+ - docs/ai-reference/AI-AUTH.md (auth is required)
39
+ - docs/ai-reference/AI-GROUPING.md (primary reference)
40
+
41
+ ## Reference Examples
42
+ - examples/vue-layouts/ (complete CRUD with modal)
43
+
44
+ ## Your Capabilities
45
+ 1. List and filter layouts with getLayouts()
46
+ 2. Get layout details with getLayout()
47
+ 3. Create new layouts with createLayout()
48
+ 4. Update layouts with updateLayout()
49
+ 5. Delete layouts with deleteLayout()
50
+ 6. Manage layout panes (add/remove cameras)
51
+ 7. Configure layout settings (columns, aspect ratio, borders)
52
+
53
+ ## Key Types
54
+
55
+ ### Layout Interface
56
+ ```typescript
57
+ interface Layout {
58
+ id: string
59
+ name: string
60
+ accountId: string
61
+ panes: LayoutPane[]
62
+ settings: LayoutSettings
63
+ effectivePermissions?: LayoutPermissions
64
+ resourceCounts?: { cameras?: number }
65
+ }
66
+
67
+ interface LayoutPane {
68
+ id: number
69
+ name: string
70
+ type: 'preview' | 'compositePreview'
71
+ size: 1 | 2 | 3
72
+ cameraId: string
73
+ }
74
+
75
+ interface LayoutSettings {
76
+ showCameraBorder: boolean
77
+ showCameraName: boolean
78
+ cameraAspectRatio: '16x9' | '4x3'
79
+ paneColumns: number // 1-6
80
+ }
81
+ ```
82
+
83
+ ### ListLayoutsParams
84
+ ```typescript
85
+ interface ListLayoutsParams {
86
+ pageSize?: number
87
+ pageToken?: string
88
+ include?: ('effectivePermissions' | 'resourceCounts' | 'resourceStatusCounts')[]
89
+ name__contains?: string
90
+ q?: string
91
+ }
92
+ ```
93
+
94
+ ## Key Functions
95
+
96
+ ### getLayouts()
97
+ List layouts with optional filters.
98
+
99
+ ```typescript
100
+ import { getLayouts, type Layout, type ListLayoutsParams } from 'een-api-toolkit'
101
+
102
+ const layouts = ref<Layout[]>([])
103
+
104
+ // Get all layouts
105
+ async function fetchLayouts() {
106
+ const result = await getLayouts({
107
+ include: ['resourceCounts', 'effectivePermissions'],
108
+ pageSize: 100
109
+ })
110
+
111
+ if (result.data) {
112
+ layouts.value = result.data.results
113
+ }
114
+ }
115
+
116
+ // Search layouts by name
117
+ async function searchLayouts(query: string) {
118
+ const result = await getLayouts({
119
+ q: query,
120
+ include: ['resourceCounts']
121
+ })
122
+
123
+ if (result.data) {
124
+ layouts.value = result.data.results
125
+ }
126
+ }
127
+ ```
128
+
129
+ ### getLayout(id, params?)
130
+ Get a specific layout:
131
+ ```typescript
132
+ import { getLayout, type Layout } from 'een-api-toolkit'
133
+
134
+ async function fetchLayout(layoutId: string) {
135
+ const result = await getLayout(layoutId, {
136
+ include: ['effectivePermissions', 'resourceStatusCounts']
137
+ })
138
+
139
+ if (result.error) {
140
+ if (result.error.code === 'NOT_FOUND') {
141
+ console.error('Layout not found')
142
+ }
143
+ return null
144
+ }
145
+
146
+ return result.data
147
+ }
148
+ ```
149
+
150
+ ### createLayout(params)
151
+ Create a new layout:
152
+ ```typescript
153
+ import { createLayout, type CreateLayoutParams, type LayoutSettings } from 'een-api-toolkit'
154
+
155
+ async function handleCreateLayout(name: string, cameraIds: string[]) {
156
+ const settings: LayoutSettings = {
157
+ showCameraBorder: true,
158
+ showCameraName: true,
159
+ cameraAspectRatio: '16x9',
160
+ paneColumns: 3
161
+ }
162
+
163
+ const panes = cameraIds.map((cameraId, index) => ({
164
+ id: index + 1,
165
+ name: `Camera ${index + 1}`,
166
+ type: 'preview' as const,
167
+ size: 1 as const,
168
+ cameraId
169
+ }))
170
+
171
+ const result = await createLayout({
172
+ name,
173
+ settings,
174
+ panes
175
+ })
176
+
177
+ if (result.error) {
178
+ console.error('Failed to create layout:', result.error.message)
179
+ return null
180
+ }
181
+
182
+ return result.data
183
+ }
184
+ ```
185
+
186
+ ### updateLayout(id, params)
187
+ Update an existing layout:
188
+ ```typescript
189
+ import { updateLayout, type UpdateLayoutParams } from 'een-api-toolkit'
190
+
191
+ async function handleUpdateLayout(layoutId: string, updates: UpdateLayoutParams) {
192
+ const result = await updateLayout(layoutId, updates)
193
+
194
+ if (result.error) {
195
+ console.error('Failed to update layout:', result.error.message)
196
+ return false
197
+ }
198
+
199
+ return true
200
+ }
201
+
202
+ // Update name
203
+ await handleUpdateLayout('layout-123', { name: 'New Name' })
204
+
205
+ // Update settings
206
+ await handleUpdateLayout('layout-123', {
207
+ settings: { paneColumns: 4 }
208
+ })
209
+
210
+ // Replace panes
211
+ await handleUpdateLayout('layout-123', {
212
+ panes: [
213
+ { id: 1, name: 'New Pane', type: 'preview', size: 1, cameraId: 'cam-123' }
214
+ ]
215
+ })
216
+ ```
217
+
218
+ ### deleteLayout(id)
219
+ Delete a layout:
220
+ ```typescript
221
+ import { deleteLayout } from 'een-api-toolkit'
222
+
223
+ async function handleDeleteLayout(layoutId: string) {
224
+ const result = await deleteLayout(layoutId)
225
+
226
+ if (result.error) {
227
+ if (result.error.code === 'FORBIDDEN') {
228
+ console.error('No permission to delete this layout')
229
+ }
230
+ return false
231
+ }
232
+
233
+ return true
234
+ }
235
+ ```
236
+
237
+ ## Complete Layout Manager Component
238
+
239
+ ```vue
240
+ <script setup lang="ts">
241
+ import { ref, onMounted } from 'vue'
242
+ import {
243
+ getLayouts,
244
+ createLayout,
245
+ updateLayout,
246
+ deleteLayout,
247
+ type Layout,
248
+ type LayoutSettings,
249
+ type ListLayoutsParams
250
+ } from 'een-api-toolkit'
251
+
252
+ const layouts = ref<Layout[]>([])
253
+ const loading = ref(false)
254
+
255
+ const defaultSettings: LayoutSettings = {
256
+ showCameraBorder: true,
257
+ showCameraName: true,
258
+ cameraAspectRatio: '16x9',
259
+ paneColumns: 3
260
+ }
261
+
262
+ async function fetchLayouts() {
263
+ loading.value = true
264
+
265
+ const params: ListLayoutsParams = {
266
+ include: ['effectivePermissions', 'resourceCounts'],
267
+ pageSize: 100
268
+ }
269
+
270
+ const result = await getLayouts(params)
271
+
272
+ if (result.data) {
273
+ layouts.value = result.data.results
274
+ }
275
+
276
+ loading.value = false
277
+ }
278
+
279
+ async function handleCreate(name: string) {
280
+ const result = await createLayout({
281
+ name,
282
+ settings: defaultSettings,
283
+ panes: []
284
+ })
285
+
286
+ if (result.data) {
287
+ await fetchLayouts()
288
+ }
289
+
290
+ return result
291
+ }
292
+
293
+ async function handleUpdate(layoutId: string, name: string) {
294
+ const result = await updateLayout(layoutId, { name })
295
+
296
+ if (!result.error) {
297
+ await fetchLayouts()
298
+ }
299
+
300
+ return result
301
+ }
302
+
303
+ async function handleDelete(layoutId: string) {
304
+ if (!confirm('Delete this layout?')) return
305
+
306
+ const result = await deleteLayout(layoutId)
307
+
308
+ if (!result.error) {
309
+ await fetchLayouts()
310
+ }
311
+
312
+ return result
313
+ }
314
+
315
+ onMounted(fetchLayouts)
316
+ </script>
317
+
318
+ <template>
319
+ <div class="layout-manager">
320
+ <div v-if="loading">Loading layouts...</div>
321
+
322
+ <div v-else class="layout-grid">
323
+ <div v-for="layout in layouts" :key="layout.id" class="layout-card">
324
+ <h3>{{ layout.name }}</h3>
325
+ <p>{{ layout.panes.length }} panes</p>
326
+ <p>{{ layout.settings.paneColumns }} columns</p>
327
+
328
+ <div class="actions">
329
+ <button v-if="layout.effectivePermissions?.edit" @click="handleUpdate(layout.id, 'New Name')">
330
+ Edit
331
+ </button>
332
+ <button v-if="layout.effectivePermissions?.delete" @click="handleDelete(layout.id)">
333
+ Delete
334
+ </button>
335
+ </div>
336
+ </div>
337
+ </div>
338
+ </div>
339
+ </template>
340
+ ```
341
+
342
+ ## Error Handling
343
+
344
+ | Error Code | Meaning | Action |
345
+ |------------|---------|--------|
346
+ | AUTH_REQUIRED | Not authenticated | Redirect to login |
347
+ | NOT_FOUND | Layout doesn't exist | Show "not found" message |
348
+ | FORBIDDEN | No permission | Show access denied message |
349
+ | VALIDATION_ERROR | Invalid input | Show validation error |
350
+ | API_ERROR | Server error | Show error, allow retry |
351
+
352
+ ## Layout Pane Management
353
+
354
+ When adding/removing panes, remember:
355
+ - Each pane needs a unique `id` within the layout
356
+ - `cameraId` links the pane to a camera
357
+ - `size` affects grid positioning (1=1x1, 2=2x2, 3=3x3)
358
+ - Use `type: 'preview'` for standard camera views
359
+
360
+ ```typescript
361
+ // Add a pane to existing layout
362
+ function addPane(layout: Layout, cameraId: string) {
363
+ const newId = layout.panes.length > 0
364
+ ? Math.max(...layout.panes.map(p => p.id)) + 1
365
+ : 1
366
+
367
+ const newPane = {
368
+ id: newId,
369
+ name: `Camera ${newId}`,
370
+ type: 'preview' as const,
371
+ size: 1 as const,
372
+ cameraId
373
+ }
374
+
375
+ return updateLayout(layout.id, {
376
+ panes: [...layout.panes, newPane]
377
+ })
378
+ }
379
+
380
+ // Remove a pane
381
+ function removePane(layout: Layout, paneId: number) {
382
+ return updateLayout(layout.id, {
383
+ panes: layout.panes.filter(p => p.id !== paneId)
384
+ })
385
+ }
386
+ ```
387
+
388
+ ## Constraints
389
+ - Always check authentication before API calls
390
+ - Check effectivePermissions before showing edit/delete buttons
391
+ - Layout name is required for create/update
392
+ - Settings object is required for create
393
+ - PATCH returns 204 (void), not the updated layout
394
+ - DELETE returns 204 (void) on success
@@ -76,6 +76,15 @@ assistant: "I'll use the een-media-agent to diagnose the HLS configuration and a
76
76
  | Full-quality live video | Live Video SDK | Full resolution, lowest latency |
77
77
  | Recorded video playback | HLS via `listMedia()` | Seek capability, standard player |
78
78
 
79
+ **CRITICAL: Main feeds do NOT support multipartUrl**
80
+
81
+ The EEN API only returns `multipartUrl` for **preview feeds** (`type: 'preview'`), not main feeds (`type: 'main'`).
82
+
83
+ - **Preview feeds** → Use `multipartUrl` (MJPEG in `<img>` element)
84
+ - **Main feeds** → Use **Live Video SDK** (full HD in `<video>` element)
85
+
86
+ If you need HD quality video, you MUST use the Live Video SDK. Do not attempt to use `multipartUrl` with main feeds - it won't work.
87
+
79
88
  ## Key Functions
80
89
 
81
90
  ### getLiveImage(cameraId)
@@ -117,6 +126,13 @@ async function setupMediaSession() {
117
126
  ```
118
127
 
119
128
  ### Using multipartUrl (MJPEG Stream)
129
+
130
+ **Feed Types:**
131
+ | Feed Type | Use Case | Quality |
132
+ |-----------|----------|---------|
133
+ | `preview` | Camera sidebar thumbnails, grids | Lower bandwidth, smaller resolution |
134
+ | `main` | Primary video player, HD viewing | Full quality, higher bandwidth |
135
+
120
136
  ```typescript
121
137
  // MUST call initMediaSession() first!
122
138
  import { listFeeds, initMediaSession } from 'een-api-toolkit'
@@ -125,14 +141,18 @@ onMounted(async () => {
125
141
  // Step 1: Initialize media session
126
142
  await initMediaSession()
127
143
 
128
- // Step 2: Get feeds
129
- const result = await listFeeds({ cameraId: props.cameraId })
144
+ // Step 2: Get feeds - specify type for desired quality
145
+ const result = await listFeeds({
146
+ deviceId: props.camera.id,
147
+ type: 'preview', // 'preview' for thumbnails, 'main' for HD
148
+ include: ['multipartUrl'] // Request multipartUrl to be included
149
+ })
130
150
 
131
151
  if (result.data) {
132
- const previewFeed = result.data.results.find(f => f.type === 'preview')
133
- if (previewFeed?.multipartUrl) {
152
+ const feed = result.data.results?.find(f => f.multipartUrl)
153
+ if (feed?.multipartUrl) {
134
154
  // Step 3: Use multipartUrl directly - DO NOT modify it
135
- previewImageUrl.value = previewFeed.multipartUrl
155
+ previewImageUrl.value = feed.multipartUrl
136
156
  }
137
157
  }
138
158
  })
package/CHANGELOG.md CHANGED
@@ -2,21 +2,116 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
- ## [0.3.30] - 2026-01-23
5
+ ## [0.3.35] - 2026-01-24
6
6
 
7
7
  ### Release Summary
8
8
 
9
- No PR descriptions available for this release.
9
+ #### PR #72: feat: Add Layouts API with CRUD operations
10
+ ## Summary
11
+
12
+ - Add complete Layouts API support with CRUD operations (getLayouts, getLayout, createLayout, updateLayout, deleteLayout)
13
+ - Add vue-layouts example application demonstrating full layout management UI
14
+ - Add comprehensive documentation and Claude Code agent for layouts
15
+
16
+ ## Changes
17
+
18
+ ### New Features
19
+ - **Layouts Service** (`src/layouts/service.ts`): Full CRUD operations for EEN Layouts API
20
+ - **Layout Types** (`src/types/layout.ts`): TypeScript interfaces for Layout, LayoutPane, LayoutSettings, etc.
21
+ - **vue-layouts Example**: Complete Vue 3 app with layout list, create/edit modal, camera pane management
22
+
23
+ ### Testing
24
+ - **Unit Tests**: 36 tests for layouts service (`src/__tests__/layouts.service.test.ts`)
25
+ - **E2E API Tests**: 10 tests for layouts endpoints (`e2e/layouts.spec.ts`)
26
+ - **Example App E2E**: 14 tests for vue-layouts app (`examples/vue-layouts/e2e/`)
27
+
28
+ ### Documentation
29
+ - Add `docs/ai-reference/AI-GROUPING.md` for layouts documentation
30
+ - Add `.claude/agents/een-grouping-agent.md` for AI assistance
31
+ - Update README.md, AI-CONTEXT.md, USER-GUIDE.md, DEVELOPER-GUIDE.md
32
+ - Add README.md to vue-layouts example
33
+ - Add `.github/copilot-instructions.md` with project-specific review guidelines
34
+
35
+ ### Infrastructure
36
+ - Add `apiPatch` and `apiDelete` helpers to `e2e/api-helper.ts`
37
+ - Update `scripts/generate-ai-context.ts` to include layouts
38
+
39
+ ## Test plan
40
+
41
+ - [x] Unit tests pass (36 layouts tests)
42
+ - [x] E2E API tests pass (10 layouts tests)
43
+ - [x] vue-layouts example app E2E tests pass (14 tests)
44
+ - [ ] CI pipeline validates all tests
45
+ - [ ] Manual verification of layout CRUD in example app
46
+
47
+ ## Version
48
+
49
+ 0.3.32
50
+
51
+ 🤖 Generated with [Claude Code](https://claude.ai/code)
52
+
53
+ #### PR #73: chore: Pin Playwright 1.58.0 and improve docs agent
54
+ ## Summary
55
+
56
+ - Pin Playwright to exact version 1.58.0 across all packages (root + 9 examples)
57
+ - Regenerate all package-lock.json files for consistency
58
+ - Update docs-accuracy-reviewer agent to check ALL example READMEs
59
+ - Fix een-devices-agent with correct CameraStatus/BridgeStatus values
60
+
61
+ ## Why
62
+
63
+ CI workflow was failing due to Playwright version mismatch:
64
+ - Some examples had 1.57.0, others had 1.58.0
65
+ - Browser build versions differed (1200 vs 1208)
66
+ - Tests failed with "Executable doesn't exist" error
67
+
68
+ ## Changes
69
+
70
+ | Package | Before | After |
71
+ |---------|--------|-------|
72
+ | Root | ^1.57.0 | 1.58.0 |
73
+ | All 9 examples | ^1.57.0 | 1.58.0 |
74
+
75
+ Version: **0.3.35**
76
+
77
+ ## Test Results
78
+
79
+ - **Unit tests**: 378 passed
80
+ - **E2E tests**: 137 passed across 9 example apps
81
+ - vue-alerts-metrics: 20 passed
82
+ - vue-bridges: 13 passed
83
+ - vue-cameras: 13 passed
84
+ - vue-event-subscriptions: 15 passed
85
+ - vue-events: 16 passed
86
+ - vue-feeds: 12 passed
87
+ - vue-layouts: 14 passed
88
+ - vue-media: 20 passed
89
+ - vue-users: 14 passed
90
+
91
+ 🤖 Generated with [Claude Code](https://claude.ai/code)
92
+
10
93
 
11
94
  ### Detailed Changes
12
95
 
96
+ #### Features
97
+ - feat: Add Layouts API with CRUD operations and vue-layouts example
98
+
99
+ #### Bug Fixes
100
+ - fix: Increase AUTH_COMPLETE timeout for CI stability
101
+ - fix: Address PR review medium priority issues
102
+ - fix: Address PR review comments
103
+
13
104
  #### Other Changes
14
- - refactor: Use computed property for camera status in devices agent
15
- - docs: Document status field requirements in devices agent
105
+ - chore: Pin Playwright 1.58.0 and improve docs agent
106
+ - ci: Run all example E2E tests and add Slack failure notification
107
+ - docs: Optimize example app screenshots and add to READMEs
108
+ - docs: Update copilot instructions with project-specific patterns
109
+ - docs: Add README.md to vue-layouts example
110
+ - docs: Add Layouts API to documentation
16
111
 
17
112
  ### Links
18
113
  - [npm package](https://www.npmjs.com/package/een-api-toolkit)
19
- - [Full Changelog](https://github.com/klaushofrichter/een-api-toolkit/compare/v0.3.28...v0.3.30)
114
+ - [Full Changelog](https://github.com/klaushofrichter/een-api-toolkit/compare/v0.3.30...v0.3.35)
20
115
 
21
116
  ---
22
- *Released: 2026-01-23 17:42:54 CST*
117
+ *Released: 2026-01-24 12:52:52 CST*
package/README.md CHANGED
@@ -16,7 +16,7 @@ This repository is provided as-is without any warranty, functionality guarantee,
16
16
 
17
17
  ## Key Features
18
18
 
19
- - **Plain Async Functions** - Simple API calls with `getCurrentUser()`, `getUsers()`, `getUser()`, `getCameras()`, `getCamera()`
19
+ - **Plain Async Functions** - Simple API calls with `getCurrentUser()`, `getUsers()`, `getCameras()`, `getLayouts()`, and more
20
20
  - **Secure OAuth** - Token management via proxy (refresh tokens never exposed to client)
21
21
  - **Type-Safe** - Full TypeScript types from OpenAPI spec
22
22
  - **Predictable Errors** - Always returns `{data, error}`, no exceptions thrown
@@ -140,8 +140,8 @@ if (!cameraError) {
140
140
  │ │ import from 'een-api-toolkit' │ │
141
141
  │ │ ┌────────────────────────────────────┐ │ │
142
142
  │ │ │ Plain Async Functions │ │ │
143
- │ │ │ getUsers(), getCameras() │ │ │
144
- │ │ │ getUser(), getCamera() │ │ │
143
+ │ │ │ getUsers(), getCameras(), etc. │ │ │
144
+ │ │ │ getLayouts(), createLayout()... │ │ │
145
145
  │ │ └────────────────────────────────────┘ │ │
146
146
  │ └────────────────────────────────────────────────────────────────┘ │
147
147
  │ │ │
@@ -189,6 +189,7 @@ The toolkit includes specialized [Claude Code](https://docs.anthropic.com/en/doc
189
189
  - `een-devices-agent` - Camera and bridge management
190
190
  - `een-media-agent` - Live video, previews, HLS playback
191
191
  - `een-events-agent` - Events, alerts, metrics, SSE subscriptions
192
+ - `een-grouping-agent` - Layouts CRUD operations, camera pane management
192
193
 
193
194
  **Installation:**
194
195
  ```bash
@@ -208,6 +209,7 @@ The `examples/` directory contains complete Vue 3 applications demonstrating too
208
209
  | **[vue-users](./examples/vue-users/)** | User management with pagination | `getUsers()`, `getCurrentUser()` |
209
210
  | **[vue-cameras](./examples/vue-cameras/)** | Camera listing with status filters | `getCameras()` |
210
211
  | **[vue-bridges](./examples/vue-bridges/)** | Bridge listing with device info | `getBridges()` |
212
+ | **[vue-layouts](./examples/vue-layouts/)** | Layout CRUD with camera panes | `getLayouts()`, `createLayout()`, `updateLayout()`, `deleteLayout()` |
211
213
  | **[vue-media](./examples/vue-media/)** | Live and recorded image viewing | `getCameras()`, `getLiveImage()`, `getRecordedImage()` |
212
214
  | **[vue-feeds](./examples/vue-feeds/)** | Live video streaming with preview and main streams | `getCameras()`, `listFeeds()`, `initMediaSession()` |
213
215
  | **[vue-events](./examples/vue-events/)** | Event listing with bounding box overlays | `listEvents()`, `listEventTypes()`, `listEventFieldValues()`, `getRecordedImage()` |