Skill for registering new protocol extractors with the Strix config generator. Narrow in scope: adds ONE extractor + tests per protocol, never touches pkg/generate/ internals. References xiaomi as the golden reference for both the extractor pattern and the 16 mandatory test scenarios.
9.8 KiB
name, description, disable-model-invocation, argument-hint
| name | description | disable-model-invocation | argument-hint | |
|---|---|---|---|---|
| add_generate_strix | Register a new protocol extractor for the Strix config generator. Use when adding support for Tuya, Tapo, Nest, Ring, Roborock and other camera protocols that need credentials in a separate YAML section of frigate-config.yaml. This skill adds the glue; it does NOT add the protocol itself -- that's /add_protocol_strix. | true |
|
Add Generate Extractor to Strix
You are registering a new protocol with the Strix config generator so that /api/generate can produce a valid frigate-config.yaml for cameras of that protocol.
The protocol name is provided as argument (e.g. /add_generate_strix tuya). If no argument, use AskUserQuestion to ask which protocol.
This skill is NARROW. It adds ONE extractor function plus tests. Do NOT modify pkg/generate/ internals (registry.go, config.go, writer.go, insert.go) -- they are protocol-agnostic by design. If you are tempted to change them, stop and ask the user.
Repositories
- Strix: current working directory (
/home/user/Strix) - go2rtc:
/home/user/go2rtc(reference for YAML section format)
STEP 0: Study the xiaomi reference
Before writing anything, read these files completely. Xiaomi is the golden reference for this skill. Every new protocol copies its structure.
internal/xiaomi/xiaomi.go-- howextractForConfigis written and wheregenerate.RegisterExtractis called insideInit()pkg/generate/xiaomi_test.go-- the 16 test scenarios that every new protocol must passpkg/generate/registry.go-- theExtractFunccontract (do not modify, only understand)
Then glance at these to understand what NOT to touch:
pkg/generate/config.go-- howrunExtractis called for main/sub/go2rtc-override URLspkg/generate/writer.go-- howwriteCredentialsnests sections undergo2rtc:pkg/generate/insert.go--upsertCredentialsmerges into existing configs
STEP 1: Study the protocol in go2rtc
Read these files in the go2rtc repo:
internal/<proto>/README.md-- authoritative YAML formatinternal/<proto>/<proto>.go-- how go2rtc unmarshals the credentials section (look foryaml:"<proto>"in a struct tag)pkg/<proto>/-- URL scheme: what goes into userinfo, what into query, what into path
You need to answer exactly three questions:
-
Where does the secret live in the URL?
?token=Xin query (xiaomi, nest, ring)userinfopassword (tapo, roborock)- Custom (read the module)
-
What is the YAML section name? Usually the same as the URL scheme (
tuya:,tapo:). Confirm from go2rtc'syaml:struct tag. -
What is the YAML key format? Examples:
- xiaomi:
"<userID>"(quoted, matchescifield in device) - tapo:
<user>@<host> - roborock:
<username> - nest:
"<userID>"
- xiaomi:
Write these three answers down. They drive the extractor.
STEP 2: Verify the internal module already exists
Check internal/<proto>/<proto>.go exists and has a working Init() with tester.RegisterSource or similar. If the module does NOT exist, stop and tell the user to run /add_protocol_strix <proto> first. This skill only adds the generate hook to an existing module.
STEP 3: Add the extractor to internal/<proto>/<proto>.go
Open internal/<proto>/<proto>.go.
3a. Add import (if not present):
import "github.com/eduard256/strix/pkg/generate"
3b. Register the extractor at the END of Init():
generate.RegisterExtract("<proto>", extractForConfig)
3c. Add the function. Use the exact xiaomi style. Place it directly after Init().
Template: secret in query (xiaomi-like)
// extractForConfig strips ?<secret>=... from <proto>:// URL and returns
// <key> + <token> for a go2rtc:<section>: block.
// ex. <proto>://<user>:<region>@<ip>?...&<secret>=T
// -> <proto>://<user>:<region>@<ip>?..., "<section>", "<user>", "T"
func extractForConfig(rawURL string) (cleaned, section, key, value string) {
u, err := url.Parse(rawURL)
if err != nil || u.User == nil {
return rawURL, "", "", ""
}
q := u.Query()
token := q.Get("<secret>")
if token == "" {
return rawURL, "", "", ""
}
q.Del("<secret>")
u.RawQuery = q.Encode()
return u.String(), "<section>", u.User.Username(), token
}
Template: secret in userinfo password (tapo-like)
func extractForConfig(rawURL string) (cleaned, section, key, value string) {
u, err := url.Parse(rawURL)
if err != nil || u.User == nil {
return rawURL, "", "", ""
}
pw, ok := u.User.Password()
if !ok || pw == "" {
return rawURL, "", "", ""
}
// ex. tapo: key = "admin@192.168.1.100"
key = u.User.Username() + "@" + u.Host
// URL stays as-is -- go2rtc reads credentials from userinfo directly
return rawURL, "<section>", key, pw
}
Rules for the extractor (strict)
- MUST return
(rawURL, "", "", "")on any parse error or missing secret. Never return an emptycleaned. - MUST NOT log, MUST NOT touch filesystem, MUST NOT call APIs.
- MUST be deterministic -- same input always returns same output.
- MUST NOT URL-encode the token value.
writeCredentialsemits it raw; YAML parsesV1:abc+/=fine. - Keep it under 20 lines. If it grows, you're doing too much.
STEP 4: Write pkg/generate/<proto>_test.go
Copy the structure from pkg/generate/xiaomi_test.go exactly. Change:
registerXiaomi->register<Proto>(use a newsync.Onceper file -- do NOT shareregisterOnceacross files)xurl(...)-><proto>url(...)-- builds a URL in this protocol's format- Test function names:
TestXiaomi_*->Test<Proto>_* - All string literals that reference
xiaomi://,"acc1",V1:TOK_A-> equivalents for this protocol
The 16 tests to write (all MUST pass)
All scenarios from xiaomi_test.go are relevant and must be present:
| # | Test | What it verifies |
|---|---|---|
| 1 | NewConfig_SingleCamera | Nested go2rtc:\n <section>:\n <key>: <value> with correct indentation. URL in streams has no secret left. |
| 2 | SameAccount_TokenNotDuplicated | Two cameras, same key -> exactly one entry in the section. |
| 3 | TwoAccounts_SortedKeys | Two keys in the section appear in ASCII-sorted order. |
| 4 | TokenRefresh_OverwritesValue | Re-adding a camera with a new token replaces the stored value, exactly one key remains. |
| 5 | MainAndSub_SameAccount_OneToken | Main + Sub with identical credentials -> one key. |
| 6 | MainAndSub_DifferentAccounts | Main + Sub with two accounts -> two keys. |
| 7 | Scale_10Cameras_3Accounts | 10 cameras across 3 accounts sequentially added -> exactly 3 keys at the end, most-recent values. |
| 8 | URLWithoutToken_NoSection | URL missing the secret -> no <section>: header written (check "\n <section>:\n", not the URL scheme substring). |
| 9 | MalformedURL_DoesNotPanic | <proto>://%%%bad does not crash. |
| 10 | TokenSpecialChars_PreservedRaw | Secret with +, /, =, : is emitted verbatim. |
| 11 | Go2RTCOverride_PassesThroughExtractor | req.Go2RTC.MainStreamSource with this protocol is also extracted. |
| 12 | AddToConfig_NoExistingSection | Start from rtsp-only config, add this protocol -> new <section>: block created under go2rtc:. |
| 13 | AddToConfig_ExistingSection | Start from a config that already has <section>: -> new key merged, one header. |
| 14 | CustomName_URLStillClean | req.Name set, URL still has no secret in streams. |
| 15 | MixedProtocols | rtsp + this protocol together -- rtsp URL untouched, secret extracted. |
| 16 | SectionOrder | Order: go2rtc -> streams -> <section> -> cameras -> version. |
Common pitfalls in tests
assertNotContains(cfg, "<section>:")is WRONG when the URL scheme contains the same substring. Use"\n <section>:\n"instead (nested form) to avoid matching<proto>://.- For protocols where
extractForConfigreturnsrawURLunchanged (userinfo template), the "URL cleaning" assertionassertNotContains(cfg, "token=")does not apply -- skip or adjust. - Use
registerOncewithsync.Onceso running the whole package test suite twice does not duplicate-register the extractor.
STEP 5: Build and run the tests
cd /home/user/Strix
go build ./...
go test ./pkg/generate/ -v -run Test<Proto>
Every test must pass. If any fails:
- Re-read the corresponding xiaomi test and the difference in your version.
- Re-read the extractor function -- usually the bug is there, not in generate internals.
- Do NOT "fix"
pkg/generate/to make tests pass. The generator has 16 passing tests for xiaomi already -- your protocol must fit the same contract.
STEP 6: Sanity check the full generator output
Run once with a realistic URL manually (Bash + go run inline tool) and eyeball the YAML. Confirm:
go2rtc:is top-levelstreams:and<section>:are both siblings undergo2rtc:with 2-space indent- Keys under
<section>:use 4-space indent - No duplicate headers
- Secret appears verbatim (no
%2F, no%3D)
Do NOT commit. Leave changes staged for the user to review and commit manually.
ABSOLUTES -- DO NOT VIOLATE
- Never modify
pkg/generate/registry.go,config.go,writer.go,insert.go. If you think you need to, the protocol probably doesn't fit the extractor contract -- stop and discuss with the user. - Never add a new extractor to
pkg/generate/. Extractors live ininternal/<proto>/, the test file is the only thing inpkg/generate/. - Never modify
pkg/<proto>/. That code comes from go2rtc; we don't own it. - Never write tests in
internal/<proto>/. All generator tests go topkg/generate/<proto>_test.go. - Never call
generate.RegisterExtractfrom inside a test. Usesync.Once+ a helper likeregister<Proto>()inside the test file. - Never commit. Leave the changes for the user.
- Never skip tests. All 16 scenarios are mandatory -- they caught real regressions during development.