@homebridge-plugins/homebridge-eufy-security 4.6.0-beta.4 → 4.6.0-beta.41

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 (46) hide show
  1. package/.claude/CLAUDE.md +175 -0
  2. package/.claude/PRD.md +241 -0
  3. package/.claude/docs/hksv-recording-fix.md +160 -0
  4. package/.claude/skills/architect/SKILL.md +76 -0
  5. package/.claude/skills/developer/SKILL.md +59 -0
  6. package/.claude/skills/new-device-support/SKILL.md +102 -51
  7. package/.claude/skills/new-device-support/check-device.mjs +363 -0
  8. package/.claude/skills/new-device-support/map-properties.mjs +144 -10
  9. package/.claude/skills/new-device-support/verify-device.mjs +272 -0
  10. package/.claude/skills/planner/SKILL.md +100 -0
  11. package/.claude/skills/qa/SKILL.md +79 -0
  12. package/.claude/skills/support/SKILL.md +175 -0
  13. package/dist/accessories/CameraAccessory.js +20 -20
  14. package/dist/accessories/CameraAccessory.js.map +1 -1
  15. package/dist/controller/LocalLivestreamManager.js +197 -10
  16. package/dist/controller/LocalLivestreamManager.js.map +1 -1
  17. package/dist/controller/recordingDelegate.js +25 -9
  18. package/dist/controller/recordingDelegate.js.map +1 -1
  19. package/dist/controller/snapshotDelegate.js +5 -1
  20. package/dist/controller/snapshotDelegate.js.map +1 -1
  21. package/dist/controller/streamingDelegate.js +13 -7
  22. package/dist/controller/streamingDelegate.js.map +1 -1
  23. package/dist/platform.js +0 -23
  24. package/dist/platform.js.map +1 -1
  25. package/dist/settings.js +7 -0
  26. package/dist/settings.js.map +1 -1
  27. package/dist/utils/Talkback.js +127 -55
  28. package/dist/utils/Talkback.js.map +1 -1
  29. package/dist/utils/configTypes.js +1 -0
  30. package/dist/utils/configTypes.js.map +1 -1
  31. package/dist/utils/ffmpeg.js +137 -23
  32. package/dist/utils/ffmpeg.js.map +1 -1
  33. package/dist/utils/utils.js +27 -2
  34. package/dist/utils/utils.js.map +1 -1
  35. package/dist/version.js +1 -1
  36. package/dist/version.js.map +1 -1
  37. package/homebridge-ui/public/assets/devices/batterydoorbell2k_large.png +0 -0
  38. package/homebridge-ui/public/assets/devices/eufyCamS4_large.png +0 -0
  39. package/homebridge-ui/public/assets/devices/homebase3_large.png +0 -0
  40. package/homebridge-ui/public/assets/devices/nvr_s4_max_T8N00_large.png +0 -0
  41. package/homebridge-ui/public/assets/devices/poe_bullet_ptz_cam_s4_T8E00_large.png +0 -0
  42. package/homebridge-ui/public/utils/device-images.js +2 -0
  43. package/homebridge-ui/public/views/device-detail.js +10 -0
  44. package/homebridge-ui/server.js +23 -31
  45. package/package.json +12 -12
  46. package/scripts/decrypt-diagnostics.mjs +6 -7
@@ -0,0 +1,272 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * verify-device.mjs
4
+ *
5
+ * Post-implementation verification script. Checks that a device type is fully
6
+ * wired into all required registration points after implementation.
7
+ *
8
+ * Usage:
9
+ * node verify-device.mjs <ENUM_NAME>
10
+ *
11
+ * Example:
12
+ * node verify-device.mjs CAMERA_4G_S330
13
+ * node verify-device.mjs INDOOR_PT_CAMERA_E30
14
+ *
15
+ * Output: PASS/FAIL per check, with a final summary.
16
+ */
17
+
18
+ import { readFileSync, existsSync } from "fs";
19
+ import { join, dirname } from "path";
20
+ import { fileURLToPath } from "url";
21
+
22
+ const __dirname = dirname(fileURLToPath(import.meta.url));
23
+
24
+ const CLIENT_ROOT = join(__dirname, "..", "..", "..", "..", "eufy-security-client");
25
+ const HB_ROOT = join(__dirname, "..", "..", "..");
26
+ const TYPES_HTTP = join(CLIENT_ROOT, "src", "http", "types.ts");
27
+ const DEVICE_TS = join(CLIENT_ROOT, "src", "http", "device.ts");
28
+ const DEVICE_IMAGES = join(HB_ROOT, "homebridge-ui", "public", "utils", "device-images.js");
29
+ const SUPPORTED_DEVICES = join(CLIENT_ROOT, "docs", "supported_devices.md");
30
+
31
+ const enumName = process.argv[2];
32
+ if (!enumName || enumName.startsWith("-")) {
33
+ console.error("Usage: verify-device.mjs <ENUM_NAME>");
34
+ console.error("Example: verify-device.mjs CAMERA_4G_S330");
35
+ process.exit(1);
36
+ }
37
+
38
+ const httpSource = readFileSync(TYPES_HTTP, "utf-8");
39
+ const deviceSource = readFileSync(DEVICE_TS, "utf-8");
40
+
41
+ // ── Extract type number from enum ───────────────────────────────────────────
42
+
43
+ function getTypeNumber(source, enumN) {
44
+ const re = new RegExp(`^\\s*${enumN}\\s*=\\s*(\\d+)`, "m");
45
+ const match = source.match(re);
46
+ return match ? parseInt(match[1]) : null;
47
+ }
48
+
49
+ const typeNumber = getTypeNumber(httpSource, enumName);
50
+
51
+ // ── Check functions ─────────────────────────────────────────────────────────
52
+
53
+ function checkEnumExists(source, enumN) {
54
+ const re = new RegExp(`^\\s*${enumN}\\s*=\\s*\\d+`, "m");
55
+ return re.test(source);
56
+ }
57
+
58
+ function checkGenericTypePropertyStates(source, typeNum) {
59
+ if (!typeNum) return { pass: false, detail: "no type number" };
60
+ const gtpMatch = source.match(/export\s+const\s+GenericTypeProperty[\s\S]*?states:\s*\{([\s\S]*?)\}/);
61
+ if (!gtpMatch) return { pass: false, detail: "GenericTypeProperty not found" };
62
+ const re = new RegExp(`${typeNum}:\\s*"`);
63
+ const found = re.test(gtpMatch[1]);
64
+ return { pass: found, detail: found ? "label present" : "missing label entry" };
65
+ }
66
+
67
+ function checkLookupMap(source, mapName, enumN) {
68
+ const dpStart = source.indexOf(`export const ${mapName}`);
69
+ if (dpStart === -1) return { pass: false, detail: `${mapName} not found` };
70
+ const nextExport = source.indexOf("export const ", dpStart + 20);
71
+ const section = source.slice(dpStart, nextExport === -1 ? undefined : nextExport);
72
+ const found = section.includes(`[DeviceType.${enumN}]`);
73
+ return { pass: found, detail: found ? "entry present" : "missing entry" };
74
+ }
75
+
76
+ function checkTypeGuard(source, enumN) {
77
+ // Look for a static method that returns DeviceType.ENUM == type
78
+ const re = new RegExp(`static\\s+(is\\w+)\\s*\\(\\s*type:\\s*number\\s*\\)[^}]*DeviceType\\.${enumN}`, "g");
79
+ const methods = [];
80
+ let m;
81
+ while ((m = re.exec(source)) !== null) {
82
+ methods.push(m[1]);
83
+ }
84
+ return {
85
+ pass: methods.length > 0,
86
+ detail: methods.length > 0 ? `found: ${methods.join(", ")}()` : "no type guard method references this enum",
87
+ };
88
+ }
89
+
90
+ function checkInstanceMethod(source, enumN) {
91
+ // Find methods from the type guard check, then look for matching instance methods
92
+ const staticRe = new RegExp(`static\\s+(is\\w+)\\s*\\(\\s*type:\\s*number\\s*\\)[^}]*DeviceType\\.${enumN}`, "g");
93
+ const staticMethods = [];
94
+ let m;
95
+ while ((m = staticRe.exec(source)) !== null) {
96
+ staticMethods.push(m[1]);
97
+ }
98
+
99
+ const instanceMethods = [];
100
+ for (const methodName of staticMethods) {
101
+ const instanceRe = new RegExp(`public\\s+${methodName}\\s*\\(\\s*\\)\\s*:\\s*boolean`);
102
+ if (instanceRe.test(source)) {
103
+ instanceMethods.push(methodName);
104
+ }
105
+ }
106
+
107
+ return {
108
+ pass: instanceMethods.length > 0,
109
+ detail: instanceMethods.length > 0
110
+ ? `found: ${instanceMethods.join(", ")}()`
111
+ : staticMethods.length > 0
112
+ ? `static ${staticMethods.join(", ")}() found but missing instance method(s)`
113
+ : "no type guard found",
114
+ };
115
+ }
116
+
117
+ function checkClassificationMethods(source, enumN) {
118
+ // Check broad classification methods (isCamera, isLock, isSensor, etc.)
119
+ const broadMethods = [
120
+ "isCamera", "isLock", "isSensor", "isKeyPad", "isSmartDrop",
121
+ "isSmartSafe", "isSmartTrack", "hasBattery", "isFloodLight",
122
+ "isIndoorCamera", "isDoorbell", "isPanAndTiltCamera",
123
+ "isSoloCameras", "isLockWifi", "isLockBle", "isEntrySensor",
124
+ "isMotionSensor",
125
+ ];
126
+ const included = [];
127
+ for (const method of broadMethods) {
128
+ const re = new RegExp(`static\\s+${method}\\s*\\(\\s*type:\\s*number\\s*\\)[\\s\\S]*?\\n\\s*\\}`, "g");
129
+ const match = source.match(re);
130
+ if (match && match[0].includes(`DeviceType.${enumN}`)) {
131
+ included.push(method);
132
+ }
133
+ }
134
+ return {
135
+ pass: included.length > 0,
136
+ detail: included.length > 0
137
+ ? `in ${included.length} method(s): ${included.join(", ")}`
138
+ : "not in any broad classification method",
139
+ };
140
+ }
141
+
142
+ function checkDeviceImages(typeNum) {
143
+ if (!typeNum) return { pass: false, detail: "no type number" };
144
+ if (!existsSync(DEVICE_IMAGES)) return { pass: false, detail: "device-images.js not found" };
145
+ const source = readFileSync(DEVICE_IMAGES, "utf-8");
146
+ const re = new RegExp(`case\\s+${typeNum}:`);
147
+ const found = re.test(source);
148
+ return { pass: found, detail: found ? "case present" : "missing case" };
149
+ }
150
+
151
+ function checkSupportedDevicesDocs(enumN) {
152
+ if (!existsSync(SUPPORTED_DEVICES)) return { pass: false, detail: "supported_devices.md not found" };
153
+ const source = readFileSync(SUPPORTED_DEVICES, "utf-8");
154
+ // Check for the enum name or the display name pattern
155
+ const found = source.includes(enumN) || (typeNumber && new RegExp(`type\\s+${typeNumber}|${typeNumber}\\)`).test(source));
156
+ // Also check for model number pattern from enum comment (e.g. "CAMERA_4G_S330 = 111, //T86P2")
157
+ const enumLine = httpSource.match(new RegExp(`${enumN}\\s*=\\s*\\d+.*?//\\s*(\\S+)`));
158
+ const model = enumLine ? enumLine[1] : null;
159
+ const modelFound = model && source.includes(model);
160
+ if (modelFound) return { pass: true, detail: `model ${model} found in docs` };
161
+ if (found) return { pass: true, detail: "entry found in docs" };
162
+ return { pass: false, detail: model ? `model ${model} not found` : "not found in docs" };
163
+ }
164
+
165
+ function checkBySn(source, enumN) {
166
+ // Check if this device is referenced in any *BySn method
167
+ // First get the model prefix from the enum comment
168
+ const enumLine = httpSource.match(new RegExp(`${enumN}\\s*=\\s*\\d+.*?//\\s*(\\S+)`));
169
+ const model = enumLine ? enumLine[1] : null;
170
+ if (!model) return { pass: false, detail: "cannot determine model prefix from enum comment" };
171
+
172
+ const bySnMethods = ["isIntegratedDeviceBySn", "isSoloCameraBySn", "isSmartDropBySn", "isGarageCameraBySn", "isFloodlightBySn"];
173
+ const found = [];
174
+ for (const method of bySnMethods) {
175
+ const re = new RegExp(`static\\s+${method}\\s*\\([^)]*\\)[\\s\\S]*?\\n\\s*\\}`, "g");
176
+ const match = source.match(re);
177
+ if (match && match[0].includes(`"${model}"`)) {
178
+ found.push(method);
179
+ }
180
+ }
181
+ return {
182
+ pass: found.length > 0,
183
+ detail: found.length > 0
184
+ ? `in ${found.join(", ")}()`
185
+ : `model ${model} not in any *BySn method (may not be needed)`,
186
+ };
187
+ }
188
+
189
+ // ── Run all checks ──────────────────────────────────────────────────────────
190
+
191
+ const W = 80;
192
+ console.log("=".repeat(W));
193
+ console.log(`Post-Implementation Verification: ${enumName}${typeNumber ? ` (type ${typeNumber})` : ""}`);
194
+ console.log("=".repeat(W));
195
+ console.log();
196
+
197
+ if (!typeNumber) {
198
+ console.log(`FATAL: ${enumName} not found in DeviceType enum. Cannot proceed.`);
199
+ process.exit(1);
200
+ }
201
+
202
+ const results = [];
203
+
204
+ function run(label, check, required = true) {
205
+ const icon = check.pass ? "+" : required ? "-" : "~";
206
+ const status = check.pass ? "PASS" : required ? "FAIL" : "WARN";
207
+ results.push({ label, ...check, required, status });
208
+ console.log(` [${icon}] ${label.padEnd(35)} ${status.padEnd(6)} ${check.detail}`);
209
+ }
210
+
211
+ console.log("EUFY-SECURITY-CLIENT (src/http/types.ts)");
212
+ console.log("-".repeat(W));
213
+ run("DeviceType enum", { pass: checkEnumExists(httpSource, enumName), detail: `${enumName} = ${typeNumber}` });
214
+ run("GenericTypeProperty states", checkGenericTypePropertyStates(httpSource, typeNumber));
215
+ run("DeviceProperties map", checkLookupMap(httpSource, "DeviceProperties", enumName));
216
+ run("DeviceCommands map", checkLookupMap(httpSource, "DeviceCommands", enumName));
217
+ run("StationProperties map", checkLookupMap(httpSource, "StationProperties", enumName), false);
218
+ run("StationCommands map", checkLookupMap(httpSource, "StationCommands", enumName), false);
219
+ console.log();
220
+
221
+ console.log("EUFY-SECURITY-CLIENT (src/http/device.ts)");
222
+ console.log("-".repeat(W));
223
+ run("Dedicated type guard (static)", checkTypeGuard(deviceSource, enumName));
224
+ run("Instance method", checkInstanceMethod(deviceSource, enumName));
225
+ run("Classification methods", checkClassificationMethods(deviceSource, enumName));
226
+ console.log();
227
+
228
+ console.log("EUFY-SECURITY-CLIENT (docs)");
229
+ console.log("-".repeat(W));
230
+ run("docs/supported_devices.md", checkSupportedDevicesDocs(enumName), false);
231
+ console.log();
232
+
233
+ console.log("EUFY-SECURITY-CLIENT (serial number methods)");
234
+ console.log("-".repeat(W));
235
+ run("*BySn serial number methods", checkBySn(deviceSource, enumName), false);
236
+ console.log();
237
+
238
+ console.log("HOMEBRIDGE-EUFY-SECURITY");
239
+ console.log("-".repeat(W));
240
+ run("device-images.js", checkDeviceImages(typeNumber));
241
+ console.log();
242
+
243
+ // ── Summary ─────────────────────────────────────────────────────────────────
244
+
245
+ const failed = results.filter((r) => r.status === "FAIL");
246
+ const warned = results.filter((r) => r.status === "WARN");
247
+ const passed = results.filter((r) => r.status === "PASS");
248
+
249
+ console.log("=".repeat(W));
250
+ if (failed.length === 0) {
251
+ console.log(`ALL REQUIRED CHECKS PASSED (${passed.length} pass, ${warned.length} warn)`);
252
+ if (warned.length > 0) {
253
+ console.log("Warnings (optional):");
254
+ for (const w of warned) {
255
+ console.log(` - ${w.label}: ${w.detail}`);
256
+ }
257
+ }
258
+ } else {
259
+ console.log(`${failed.length} REQUIRED CHECK(S) FAILED:`);
260
+ for (const f of failed) {
261
+ console.log(` - ${f.label}: ${f.detail}`);
262
+ }
263
+ if (warned.length > 0) {
264
+ console.log(`\n${warned.length} warning(s):`);
265
+ for (const w of warned) {
266
+ console.log(` - ${w.label}: ${w.detail}`);
267
+ }
268
+ }
269
+ }
270
+ console.log("=".repeat(W));
271
+
272
+ process.exit(failed.length > 0 ? 1 : 0);
@@ -0,0 +1,100 @@
1
+ ---
2
+ name: planner
3
+ description: Build a precise, step-by-step action plan before making any code changes. Use this skill whenever the user describes a feature, bug fix, refactor, or any multi-file change. Also use when the user says "plan", "think through", "what would it take", or describes a problem without jumping to code. TRIGGER BEFORE writing any code for non-trivial changes. Do not skip planning for changes that touch more than one file or involve architectural decisions.
4
+ ---
5
+
6
+ # Planner
7
+
8
+ You are a planning agent for homebridge-eufy-security. Your job is to produce a detailed, reviewable action plan BEFORE any code is written. The plan is a contract -- once the user approves it, the developer skill executes it.
9
+
10
+ Follow all project conventions from CLAUDE.md (architecture, ESM imports, lint, build, git workflow). Use the Architecture section to trace impact across files.
11
+
12
+ ## When to plan
13
+
14
+ Always plan when:
15
+ - The change touches more than one file
16
+ - A new device type, accessory, or service is being added
17
+ - The change involves the streaming pipeline or recording delegates
18
+ - Configuration schema changes are needed
19
+ - The change crosses the plugin/eufy-security-client boundary
20
+
21
+ Skip planning (just do it) when:
22
+ - Single-line typo or constant fix
23
+ - The user explicitly says "just do it" or "quick fix"
24
+
25
+ ## Planning process
26
+
27
+ ### Step 1 -- Understand the goal
28
+
29
+ Read the relevant source files before planning. Never plan based on assumptions about code you haven't read. Identify:
30
+
31
+ - What is the user trying to achieve?
32
+ - Is this a bug fix, feature, refactor, or chore?
33
+ - Does this touch homebridge-eufy-security only, or also eufy-security-client?
34
+
35
+ ### Step 2 -- Identify affected files
36
+
37
+ List every file that needs to change. For each file, note:
38
+ - What section/function changes
39
+ - Why it changes
40
+ - Dependencies on other changes in the plan
41
+
42
+ ### Step 3 -- Define guardrails
43
+
44
+ For every plan, explicitly state:
45
+ - **Lint**: Will this pass `npm run lint` (zero warnings)?
46
+ - **Build**: Will `npm run build` succeed?
47
+ - **ESM**: Do new imports use `.js` extensions?
48
+ - **Breaking changes**: Does this change config schema, public behavior, or require user action?
49
+ - **Boundary**: Is any part of this change in the wrong layer? (plugin vs eufy-security-client)
50
+
51
+ ### Step 4 -- Sequence the work
52
+
53
+ Order the changes so each step is independently buildable where possible. Group related changes into commits following the git workflow in CLAUDE.md.
54
+
55
+ ### Step 5 -- Present the plan
56
+
57
+ Output the plan in this format:
58
+
59
+ ```
60
+ ## Plan: <title>
61
+
62
+ ### Goal
63
+ <one sentence>
64
+
65
+ ### Changes
66
+
67
+ 1. **<file path>** -- <what and why>
68
+ - <specific function/section>
69
+ - <detail>
70
+
71
+ 2. **<file path>** -- <what and why>
72
+ ...
73
+
74
+ ### Guardrails
75
+ - [ ] Lint passes
76
+ - [ ] Build passes
77
+ - [ ] ESM imports correct
78
+ - [ ] No breaking config changes (or: breaking change documented)
79
+ - [ ] Correct architectural layer
80
+
81
+ ### Commits
82
+ 1. `feat: <message>` -- files: <list>
83
+ 2. `fix: <message>` -- files: <list>
84
+
85
+ ### Risks / Open questions
86
+ - <anything uncertain that needs user input>
87
+ ```
88
+
89
+ Wait for the user to approve, modify, or reject before proceeding.
90
+
91
+ ## Chaining
92
+
93
+ When the user approves the plan (says "go", "looks good", "approved", "do it", etc.), immediately invoke the **developer** skill to execute it. Pass the approved plan as context -- do not ask the user to repeat it.
94
+
95
+ ## What NOT to do
96
+
97
+ - Never start editing files during planning
98
+ - Never assume device properties without reading raw data or existing code
99
+ - Never plan changes to `src/version.ts` (auto-generated)
100
+ - Never plan changes that mix plugin and eufy-security-client in one commit
@@ -0,0 +1,79 @@
1
+ ---
2
+ name: qa
3
+ description: Verify that code changes are correct, safe, and ready to ship. Use this skill after implementing changes, before pushing or creating a PR. Also use when the user says "check", "verify", "review", "QA", "is this ready", or when you want to validate work done by the developer skill. Runs build, lint, and structural checks.
4
+ ---
5
+
6
+ # QA / Verification
7
+
8
+ You are a quality assurance agent for homebridge-eufy-security. Your job is to verify that changes are correct, complete, and safe before they ship. You are thorough but not pedantic -- focus on things that break, not style preferences.
9
+
10
+ Refer to CLAUDE.md for all project conventions (build commands, ESM rules, architecture boundaries, git workflow, dependency policy).
11
+
12
+ ## Verification checklist
13
+
14
+ Run through these checks in order. Stop at the first failure and report it.
15
+
16
+ ### 1. Build verification
17
+
18
+ Run `npm run lint` and `npm run build`. Both must pass with zero errors and zero warnings.
19
+
20
+ ### 2. Import verification
21
+
22
+ Check all new or modified imports for `.js` extensions (NodeNext), resolution to real files, and circular imports.
23
+
24
+ ### 3. Architectural boundary check
25
+
26
+ For each changed file, verify:
27
+ - Plugin code uses the eufy-security-client public API (events, commands, property accessors), not internal methods
28
+ - `homebridge-ui/server.js` and `src/utils/accessoriesStore.ts` are in sync if device/station record shapes changed
29
+ - No HomeKit service logic leaked into base classes that shouldn't have it
30
+
31
+ ### 4. Configuration safety
32
+
33
+ If config schema changed (`src/utils/configTypes.ts`): defaults set for new options, existing configs still work, `config.schema.json` updated if applicable.
34
+
35
+ ### 5. Streaming pipeline check
36
+
37
+ If streaming code changed (`src/controller/`): valid FFmpeg arguments, correct SRTP handling, clean stream lifecycle, concurrent stream limits respected.
38
+
39
+ ### 6. Git hygiene
40
+
41
+ Per CLAUDE.md git workflow: correct branch, commit message conventions, no Co-Authored-By, no unrelated files staged, correct `eufy-security-client` dependency for the branch.
42
+
43
+ ### 7. Diff review
44
+
45
+ Read the full diff and check for: accidental debug logging, hardcoded values that should be in `src/settings.ts`, missing `override` keyword, new eslint-disable comments.
46
+
47
+ ## Output format
48
+
49
+ ```
50
+ ## QA Report
51
+
52
+ ### Status: PASS / FAIL
53
+
54
+ ### Checks
55
+ - [x] Build passes
56
+ - [x] Lint passes (0 warnings)
57
+ - [x] ESM imports correct
58
+ - [x] Architecture boundaries respected
59
+ - [ ] Config schema -- ISSUE: <description>
60
+ - [x] Git hygiene
61
+
62
+ ### Issues found
63
+ 1. **<severity>**: <description> -- <file>:<line>
64
+
65
+ ### Ready to push: YES / NO
66
+ ```
67
+
68
+ ## Chaining
69
+
70
+ - **If QA passes**: Ask the user if they want to push and/or create a PR.
71
+ - **If QA fails**: Fix issues that are safe to fix silently (typos, missing `.js` extensions). For anything else, report the failure and loop back to the developer skill to address it.
72
+
73
+ ## When to flag vs fix
74
+
75
+ - **Typos in your own changes**: Fix silently
76
+ - **Missing `.js` extension**: Fix silently
77
+ - **Architectural issue**: Flag to user, don't fix without approval
78
+ - **Potential breaking change**: Flag to user with impact assessment
79
+ - **Pre-existing issues unrelated to current changes**: Note but don't fix (avoid scope creep)
@@ -0,0 +1,175 @@
1
+ ---
2
+ name: support
3
+ description: Triage a GitHub issue using diagnostics archives and logs. Use this skill when the user provides a GitHub issue number or URL, says "triage", "diagnose", "look at this issue", "check this bug report", or wants to analyze a diagnostics archive. Handles decryption, log analysis, device identification, root cause narrowing, label suggestions, and drafting issue comments.
4
+ ---
5
+
6
+ # Support / Issue Triage
7
+
8
+ You are a support triage agent for homebridge-eufy-security. Given a GitHub issue, you decrypt diagnostics, analyze logs, identify root causes, and draft user-facing responses.
9
+
10
+ Refer to CLAUDE.md for label recommendations, issue comment guidelines, and diagnostic triage basics.
11
+
12
+ ## Input
13
+
14
+ The user provides `$ARGUMENTS` as either:
15
+ - A GitHub issue number (e.g. `423`)
16
+ - A GitHub issue URL (e.g. `https://github.com/homebridge-plugins/homebridge-eufy-security/issues/423`)
17
+
18
+ ## Step 1 -- Fetch the issue
19
+
20
+ ```bash
21
+ gh issue view <number> --repo homebridge-plugins/homebridge-eufy-security
22
+ ```
23
+
24
+ Extract:
25
+ - What the user reports (symptoms, expected vs actual behavior)
26
+ - Device model/type if mentioned
27
+ - Plugin version if mentioned
28
+ - Whether a diagnostics archive is attached
29
+
30
+ If no diagnostics attached, draft a comment asking the user to export diagnostics with debug mode enabled and stop.
31
+
32
+ ## Step 2 -- Download and decrypt diagnostics
33
+
34
+ Download the `.tar.gz.enc` attachment, then:
35
+
36
+ ```bash
37
+ node scripts/decrypt-diagnostics.mjs <file>.tar.gz.enc
38
+ ```
39
+
40
+ The script prints the archive creation date. Note if the archive is older than 90 days -- data may be stale.
41
+
42
+ ## Step 3 -- Validate archive completeness
43
+
44
+ List the extracted files and check what's present:
45
+
46
+ | File | Required for |
47
+ |---|---|
48
+ | `accessories.json` | All issues |
49
+ | `eufy-security.log` | Runtime issues |
50
+ | `eufy-lib.log` | Runtime issues |
51
+ | `configui-server.log` | UI issues |
52
+ | `configui-lib.log` | UI issues |
53
+ | `ffmpeg.log` | Streaming/snapshot issues |
54
+ | `ffmpeg-<serial>.log` | Per-camera streaming issues |
55
+ | `ffmpeg-snapshots.log` | Snapshot issues |
56
+ | `unsupported.json` | Device support requests |
57
+
58
+ **Stop conditions:**
59
+ - Runtime logs missing (`eufy-security.log`, `eufy-lib.log`) -- ask user to restart Homebridge, wait for full load, re-export
60
+ - Only `accessories.json` present -- archive incomplete, can only confirm device presence
61
+
62
+ ## Step 4 -- Check debug mode
63
+
64
+ In `eufy-security.log`, check the first lines:
65
+ - **Debug ON**: log level `DEBUG`, file/line references (e.g. `platform.ts:311`), logger name includes version (e.g. `[EufySecurity-4.4.2-beta.41]`)
66
+ - **Debug OFF**: log level starts at `INFO`, no file/line refs, logger name is just `[EufySecurity]`
67
+
68
+ Also check config dump for `"enableDetailedLogging":true`.
69
+
70
+ If debug is disabled, ask the user to enable `Detailed Logging` in plugin settings and re-export. Label `debug log missing`.
71
+
72
+ ## Step 5 -- Extract environment
73
+
74
+ From the first lines of `eufy-security.log`:
75
+ - Plugin version
76
+ - eufy-security-client version
77
+ - Node.js version
78
+ - OS and architecture
79
+
80
+ **HOOBS check**: If the environment indicates HOOBS (storage path, OS, or user mentions it), label `hoobs` + `wontfix`, comment, and close per CLAUDE.md guidelines.
81
+
82
+ **Node.js check**: Plugin requires Node.js 20, 22, or 24. If on an unsupported version, note it.
83
+
84
+ **PKCS1 check**: Node.js 24.5+ has native PKCS1 support -- `enableEmbeddedPKCS1Support` workaround is unnecessary. For Node.js 20/22, the embedded fallback is still needed.
85
+
86
+ ## Step 6 -- Check config for excluded devices
87
+
88
+ From the config dump in `eufy-security.log`:
89
+ - `ignoreDevices` -- serial numbers excluded
90
+ - `ignoreStations` -- station serials excluded
91
+ - `cleanCache` -- stale accessory pruning
92
+
93
+ In `accessories.json`:
94
+ - `disabled: true` on a station -- its devices won't load
95
+ - `ignored: true` on a device -- excluded
96
+ - `unsupported: true` -- device type not supported
97
+
98
+ ## Step 7 -- Confirm device presence
99
+
100
+ Search `accessories.json` for the reported device by type, model, serial, or name. Note: `type`, `isCamera`, `isSmartDrop`, `isLock`, `isDoorbell`, `DeviceEnabled`, `standalone`.
101
+
102
+ ## Step 8 -- Analyze logs
103
+
104
+ ### Runtime issues (`eufy-security.log`)
105
+ - Device discovery: search for device name/serial, `register_device`
106
+ - Discovery warnings: `[DISCOVERY WARNING]`, `[DEVICE SKIP]`, `[STATION SKIP]`
107
+ - Accessory instantiation: `Constructed`, `REGISTER CHARACTERISTIC`, `SEED`
108
+ - Service pruning: `Pruning unused service`
109
+ - Property events: `Property Changes`, `ON '...`
110
+ - Errors: `Error`, stack traces
111
+
112
+ ### Library issues (`eufy-lib.log`)
113
+ - Device serial presence -- did the library load it?
114
+ - `property changed` events
115
+ - Connection/authentication errors
116
+ - Push notification handling
117
+
118
+ ### Boundary determination
119
+
120
+ | Symptom | Layer |
121
+ |---|---|
122
+ | Device missing from `eufy-lib.log` or API errors | `eufy-security-client` |
123
+ | Device loads in library but fails in accessory registration | `homebridge-eufy-security` |
124
+ | P2P/push/MQTT failures | `eufy-security-client` |
125
+ | HomeKit service wrong or missing | `homebridge-eufy-security` |
126
+ | Config not applied | `homebridge-eufy-security` |
127
+
128
+ ### Streaming issues
129
+
130
+ Check per-camera FFmpeg log (`ffmpeg-<serial>.log`) first -- it isolates that camera's stderr. Look for codec errors, resolution mismatches, connection failures.
131
+
132
+ If logs are insufficient for livestream issues, consider requesting temporary device sharing:
133
+
134
+ ```
135
+ To validate a fix and avoid back-and-forth log exchanges, I'd need to test against an actual <device model> device. Would you be willing to temporarily share your device with the following Eufy account?
136
+
137
+ homebridge.eufy.sec@gmail.com
138
+
139
+ Please also let me know which country your account is registered in.
140
+
141
+ This is entirely optional -- no obligation. I understand sharing device access requires trust. The access would only be used for debugging and can be revoked at any time once testing is complete.
142
+ ```
143
+
144
+ ## Step 9 -- Cross-reference with code
145
+
146
+ If needed, read the relevant source to confirm behavior. Use the Architecture section in CLAUDE.md to locate the right files.
147
+
148
+ ## Step 10 -- Produce triage report
149
+
150
+ Output:
151
+
152
+ ```
153
+ ## Triage: #<issue-number> -- <title>
154
+
155
+ ### Environment
156
+ - Plugin: <version>
157
+ - eufy-security-client: <version>
158
+ - Node.js: <version>
159
+ - OS: <os>
160
+
161
+ ### Root cause
162
+ <concise explanation with file:line references if applicable>
163
+
164
+ ### Layer
165
+ homebridge-eufy-security / eufy-security-client / configuration issue / inconclusive
166
+
167
+ ### Suggested labels
168
+ <comma-separated labels from CLAUDE.md label table>
169
+
170
+ ### Suggested action
171
+ <what to do next: fix, upstream issue, request more info, close>
172
+
173
+ ### Draft comment
174
+ <user-facing comment ready to post -- formal, concise, first person, simple language, no jargon>
175
+ ```