feat: residency + components add (completes v0 feature parity) - #115
Merged
Conversation
residency: show or set the data-residency region, stored in ~/.elevenlabs/config.json. v0 built its own HTTP client so it could route from the stored setting directly; the framework instead resolves the base URL from --base-url / ELEVENLABS_BASE_URL with no config-file hook. So register() exports the region's base URL before CliApp::run reads the env, which makes a configured residency route every command, generated API commands included. Precedence: --base-url > ELEVENLABS_BASE_URL (env or .env) > stored residency > default. Registered top-level rather than under `auth` (where v0 had it) because the framework grafts its own `auth` group after custom commands and dispatches it first, so a nested subcommand would never reach the handler. components add: install ElevenLabs UI components via shadcn. Unlike v0, which interpolated the component name into a shell string (`shell: true`), the argv is passed separately and never through a shell, and the name is restricted to registry-slug characters — so it can't inject arguments or traverse paths. Windows goes through `cmd /C` since npx is a .cmd shim. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
residency.rs and settings.rs shipped untested while components.rs had all three tests; the residency half carries the riskier logic (env precedence and the region→URL mapping, which v0 covered in residency.test.ts). Extracted two pure functions so the logic is testable without mutating process env or touching a real home directory (env mutation is racy under Rust's parallel test runner): - residency::base_url_to_export(existing_env, region) — the precedence decision. Also fixes a latent bug found while writing the tests: an ELEVENLABS_BASE_URL set to an empty string used to suppress the residency and then break every request; it now counts as unset. - settings::merge_residency(existing_config, region) — the config merge, covering that unrelated keys survive and that an api_key is never persisted. Plus the full region→URL table and an unknown-region fallback. 10 new tests (22 → 32). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
.fernignore audit — verified with a real gitignore matcher, in both directions (every hand-written file protected; every generated file still regenerable). One genuine gap found: the generator emits .github/workflows/ci.yml *with* actions-rust-lang/setup-rust-toolchain, which this org's Actions allowlist rejects. The next regeneration would therefore have silently reverted the CI fix and put every run back to startup_failure. Added it, plus the hand-written elevenlabs-types/.gitignore. release.yml is deliberately left regenerable so cargo-dist updates still land. Test coverage — agents.rs, tools.rs and util.rs had none. Added tests for the pure logic in each, and removed a triplication while doing it: the pull create/update/skip decision was copy-pasted across agents/tools/tests, so it now lives in util::plan_pull_action with the flag matrix covered once (including v0's quirk that --update suppresses new items even alongside --all). Also covered: build_pulled_config (server metadata must not leak into the config file; workflow omitted when null), tool/test id spelling tolerance, default tool shapes incl. verbatim header names, and dry_run_flag/opt_string. 47 workflow tests, every module covered except mod.rs (registration wiring). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The last two v0 features. With these, v1 has full v0 feature parity.
residencyThe interesting part: v0 built its own HTTP client, so it routed straight from the stored setting. The framework resolves the base URL only from
--base-url/ELEVENLABS_BASE_URLand exposes no config-file hook — so a stored region would normally affect nothing.The bridge:
custom::registerruns beforeCliApp::run, which is where the framework reads the env. Soregisterexports the region's base URL when nothing more explicit is set. A configured residency therefore routes every command, generated API commands included.Verified with
--dry-runon a generated command:eu-residencyhttps://api.eu.residency.elevenlabs.io/v1/modelsELEVENLABS_BASE_URL=https://example.testhttps://example.test/v1/models--base-url https://flag.testhttps://flag.test/v1/modelsSo precedence is
--base-url>ELEVENLABS_BASE_URL(env or.env) > stored residency > default..envis handled explicitly: the framework loads it insiderun()and dotenvy never overrides an existing var, soregisterloads it first to avoid clobbering a.env-provided base URL.Placement note: registered top-level, not under
authas in v0. The framework grafts its ownauthgroup after custom commands (app.rs:982) and dispatches auth before custom commands (1067vs1078), soauth residencywould have produced a duplicate group whose subcommand never reached the handler.components addelevenlabs components add # all elevenlabs components add conversation-barDelegates to
npx shadcn@latest add <registry-url>with stdio inherited so prompts work.Hardened vs v0: v0 built a shell string and ran it with
shell: true, interpolating the component name — a command-injection vector. Here the argv is passed separately (never through a shell) and the name is restricted to registry-slug characters. Rejected:a;rm -rf /,$(whoami),a&&b,../../etc/passwd,a/b,.hidden,... Windows goes throughcmd /Cbecausenpxis a.cmdshimstd::process::Commandwon't resolve alone. Missing npx gives a friendly message rather than a raw OS error.Verified
cargo buildgreen; 22 workflow tests pass (3 new, covering slug validation, injection/traversal rejection, and that the argv never goes through a shell). Smoke-tested in an isolatedHOME: residency get/set/persist/validate, the routing table above, component validation, and the missing-npx path.Remaining for v1
Task #7 only: port the remaining high-value v0 unit tests, an E2E smoke test in CI, docs polish, then tag
1.0.0.🤖 Generated with Claude Code