feat(extensions): manual source binding for locally-installed skills#56
Merged
Conversation
Add service::bind_pack as the single write path for the detail panel's source field. Synthesizes a "manual" install_meta when the user binds a locally-scanned skill to a GitHub repo, leaving real git/marketplace install_meta untouched so a user-typed pack only opts in sourceless rows without overriding HK-managed installs. normalize_pack accepts owner/repo, HTTP(S) URLs, schemeless github.com paths, and SSH clone URLs, delegating URL parsing to the existing scanner::extract_pack_from_url. A github-only gate prevents non-github paste forms (gitlab.com/foo, host.tld/x) from being synthesized into wrong github URLs. migrate_pack_to_manual_meta provides a one-shot path for rows that had pack set before this feature shipped โ idempotent on subsequent calls, safe to invoke at the top of every check_updates. Both desktop and web settings handlers now funnel update_pack / batch_update_pack through service::bind_pack. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wire the new "manual" install_type into the update pipeline: - check_updates buckets "manual" alongside "git" / "marketplace" so manually-bound skills participate in update detection. - After clone, verify the named skill actually lives in the repo (mirrors the marketplace-skill subpath check). A user can paste any GitHub URL into the source field โ if their skill name doesn't exist in that repo, surface RemovedFromRepo immediately instead of waiting for the user to click Update. - update_extension whitelist accepts "manual" so the update flow succeeds for manually-bound skills. After update the row's install_type stays "manual" so the user can still unbind later. - Call migrate_pack_to_manual_meta at the top of check_updates so legacy pre-feature rows (pack present, no install_meta) opt in automatically on the next user-triggered check. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the always-editable "No source" input with a three-state pill: - Empty: "+ Bind source" CTA button. - Editing: input with autofocus; Enter commits with validation toast on invalid input, Esc cancels, blur silently dismisses. Client-side normalizes URLs to owner/repo and validates before write โ mirrors the backend's normalize_pack / is_valid_pack_format. - Bound: GitHub link chip with pencil icon to re-edit. Pill resolves the link text via groupOwnerRepo (pack โ source.url โ install_meta.url โ CLI child install_meta) so it covers the union of every place source info can live. Drop the redundant globe-link row from the INFO section โ the pill now covers that display. Add an error row to surface check_updates failures (network errors, missing repos) so users can see why a check failed instead of the Error status being dropped silently. extension-store.updatePack now refetches extensions after the API call so install_meta side effects are reflected; drops stale update statuses for affected rows so warnings don't outlive the binding they referred to; re-resolves selectedId via stable instance ids so the detail panel survives the extensionGroupKey change that binding triggers. Fix an unrelated useEffect dep bug where listing the full `group` object in the dep array caused the effect to re-fire on every render (grouped() rebuilds the object reference each time) and silently exit edit mode mid-edit. Cmd+A is intercepted in the source input so it selects only the input contents instead of bubbling to the document and selecting the whole page (a Tauri WebView quirk that affects any form-less input). i18n: 5 new keys (bindSource, bindSourcePlaceholder, editSource, bindSourceSuccess, bindSourceInvalid) + updateCheckFailed for the new error row. Drop unused noSource key. Co-Authored-By: Claude Opus 4.7 (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.
Summary
install_type = "manual"throughcheck_updates(bucket + skill-name verification) andupdate_extension(whitelist) so manually-bound rows are first-class in the update pipeline.How it works
service::bind_packis the single write path for the detail panel's source field. It synthesizesinstall_meta { install_type: "manual", url, revision: None }for sourceless rows and refreshes/clears it when the user edits/unbinds. Realgit/marketplaceinstall_meta is never touched โ a user-typed pack is just a grouping hint when a real install exists.normalize_packaccepts: bareowner/repo,https://github.com/owner/repo[.git][/tree/main], schemelessgithub.com/..., andgit@github.com:owner/repo.git. Non-github paste forms (e.g.gitlab.com/foo) are rejected by the validator so we never synthesize a wronghttps://github.com/...URL.migrate_pack_to_manual_metais invoked at the top of everycheck_updates; idempotent, opts in pre-feature rows that had pack typed before this flow existed.check_updatesclones the bound repo and verifies the skill name actually lives in it. If not (typo / wrong repo), surfacesRemovedFromRepoimmediately rather than waiting for the user to click Update and hit the same check.+ Bind sourceCTA โ click โ input (autofocus, Enter commits with toast on invalid, Esc cancels, blur silently dismisses) โ success โ link chip + pencil to re-edit.UpdateStatus::Error(network errors, missing repos, etc.) โ previously dropped silently in the UI.Test plan
cargo test -p hk-coreโ 432 / 432 pass (+18 new tests covering normalize, validate, bind_pack matrix, migrate)npm run testโ 164 / 164 vitest (+15 helper tests fornormalizePack/isValidPackFormat/groupOwnerRepoincl. CLI fallback)npm run lintโ cleancargo check --workspaceโ all 4 crates compilewebapp-testing/SKILL.mdwith placeholder content โ bound toanthropics/skillsโ Check Updates correctly flagged update_available (not removed_from_repo, since the skill exists in that repo) โ Update succeeded; version chip appeared; local SKILL.md was replaced with the realwebapp-testingcontent from the repo.owner/repogitlab.com/foo/google.com/foo) all showtoast.warningand stay in editremoved_from_repowarninguseEffectdep bug that was racing the edit toggle)Refs #39 (A3).
๐ค Generated with Claude Code