Skip to content

Release: develop → main (v0.15.0 — Claude Code marketplace) - #1172

Merged
chronoai-shining merged 58 commits into
mainfrom
develop
Jun 30, 2026
Merged

Release: develop → main (v0.15.0 — Claude Code marketplace)#1172
chronoai-shining merged 58 commits into
mainfrom
develop

Conversation

@chronoai-shining

Copy link
Copy Markdown
Collaborator

Release promotion: develop → main

Promotes the accumulated work since the last release to main, which kicks off the changeset-release workflow (version bump → tag → GitHub Release → sync-back).

Note on version state: main was a version behind (v0.13.0) while develop already carried the v0.14.0 bump + tag. This promotion catches main up; the bot will bump to v0.15.0, consuming the 9 pending changesets.

Highlights (full notes in .github/release-notes-20260630.md)

  • Public skill mirror now doubles as a Claude Code plugin marketplace.
  • Skillsets exportable as curated multi-skill plugins (opt-in card + modal).
  • Auto-managed skillset revisions; public-member-subset export; immediate mirror sync.
  • Install-command tabs on skill + skillset pages.

Release notes file present → check-release-notes gate satisfied.

Pure, deterministic generators that render a list of skills into a
Claude Code `.claude-plugin/marketplace.json` catalogue plus a
per-skill `.claude-plugin/plugin.json`.

Kept free of DB/runtime types (operates on a small MarketplaceSkillInput
shape) so the logic unit-tests in isolation and stays out of the
already-large mirrorService.ts. Plugins are sorted by name and JSON is
serialized with stable key order + trailing newline: the GitHub mirror
skips no-op commits by comparing git blob SHAs, so non-deterministic
output here would manufacture a churn commit on every sync.

Part of #1153.
Wire the manifest generators into MirrorService so the existing public-
skill GitHub mirror (`ChronoAIProject/ornn-skills`) doubles as a Claude
Code plugin marketplace — users can `/plugin marketplace add <owner>/
<repo>` then `/plugin install <skill>@<marketplace>`.

reconcileAll emits the root `.claude-plugin/marketplace.json` plus each
folder's `.claude-plugin/plugin.json` in the same desired-state diff.
The incremental publishSkill / removeSkill paths also refresh the root
manifest in their own commit: the catalogue is a function of the WHOLE
public set, so a single-skill change would otherwise leave it stale
until the next full reconcile. Unsafe-named skills (#807) are filtered
out of the manifest, mirroring the per-folder traversal guard.

Export-only and additive: no change to the registry of record, the
/api/v1 contract, or the permission model. Ornn stays the model-
agnostic backend; the marketplace is one downstream distribution
adapter on top of the mirror.

Part of #1153.
Minor bump — the public mirror now also serves a Claude Code plugin
marketplace.
Introduce the per-skillset plugin-export opt-in: an owner may flag a
skillset to be exported as ONE curated multi-skill Claude Code plugin in
the public mirror. This commit adds the wire + model surface only:

- `exportAsPlugin` (optional boolean) on the create + publish Zod schemas;
  the service defaults a missing value to OFF, so the field is optional on
  the wire and never breaks existing callers.
- `exportAsPlugin?: boolean` on `SkillsetDocument` (absent ⇒ false on
  pre-feature docs).

Eligibility to actually export = `memberVisibilityState === "all-public"`
AND this flag; that gate is enforced downstream in the repo + mirror.

Part of #1155.
Thread the plugin-export opt-in through the skillset identity repository:

- `create` persists `exportAsPlugin` (default OFF); `update` writes it only
  when an explicit boolean is given, so a publish that omits the flag
  preserves the current setting rather than silently resetting it.
- `mapDoc` reads it back (absent ⇒ false).
- `findAllEligibleForMirror()` returns the skillsets safe + opted-in to
  export — `memberVisibilityState: "all-public"` AND `exportAsPlugin: true`.
  The all-public guarantee is what makes publishing member content to the
  public mirror safe.
- A compound index backs the eligible lookup.

Part of #1155.
…1155)

Surface the plugin-export opt-in on the skillset service:

- `createSkillset` defaults `exportAsPlugin` to OFF; `publishVersion`
  forwards it only when explicitly set, preserving the prior value on omit.
- The detail-response mapper now emits `exportAsPlugin`, and the
  `SkillsetDetailResponse` type makes it a required field (always present).
- New `getLatestForMirror(guid)` returns the latest version's member refs +
  master prompt for the mirror's plugin export — no closure walk, just the
  raw refs the mirror resolves itself under SYSTEM.

Part of #1155.
…1155)

Second export layer on top of #1154's per-skill plugins: an all-public,
opted-in skillset becomes ONE curated multi-skill Claude Code plugin in
the mirror, under a dedicated `skillsets/<name>/` subtree.

- Generalize the marketplace generator: a catalogue entry now carries its
  `source` path explicitly (`MarketplacePluginInput`), so a per-skill plugin
  passes `./<name>` while a skillset passes `./skillsets/<name>` — both land
  in the same root marketplace.json. `buildPluginJson` is unchanged.
- New `skillsetPlugin` module (pure + deterministic) assembles a skillset's
  file map: `.claude-plugin/plugin.json`, `skills/<member>/…` (each member's
  package verbatim), and a README carrying the master prompt (README-only,
  never an invokable skill), member list, and install snippet. No root
  SKILL.md; no timestamp; members sorted + de-duped + safe-name filtered.
- The mirror `reconcileAll` enumerates eligible skillsets, resolves their
  members under SYSTEM, stages the subtrees, and merges the skillset entries
  into the shared marketplace.json. `appendMarketplaceManifestChange` (the
  skill incremental publish/remove path) also includes the skillset entries
  so a skill edit never drops skillsets from the catalogue.
- An in-flight guard coalesces concurrent reconciles so the new
  mutation-driven triggers can't race a cron sweep against the same head.

The skillset deps are optional on the service so existing constructions and
configs that don't wire skillsets degrade gracefully (no skillset export).

Part of #1155.
Eligibility to export a skillset can shift on a skillset mutation (opt-in
flip, member-set change, deletion) OR a skill-visibility change (a member
going private flips the skillset off all-public). Wire fire-and-forget
reconcile triggers so the mirror catches up without waiting for the cron:

- Skillset routes take a `fireMirrorReconcile` hook, fired after a
  successful create / publish / delete.
- `wireSkillsets` exposes the skillset repo + threads the hook; bootstrap
  now wires skillsets BEFORE the mirror so the mirror takes the skillset
  repo + service as deps, then fills the reconcile hook once it exists.
- The existing skill-visibility recompute hook now also fires the mirror
  reconcile — a member privacy flip both recomputes derived visibility and
  refreshes the plugin-export catalogue.

All triggers catch + log; none block the mutation response.

Part of #1155.
Mirror the backend surface on the frontend type layer:

- `exportAsPlugin` (required) on `SkillsetDetail` — drives the form toggle
  state and the read-only install hint.
- `exportAsPlugin?` on `CreateSkillsetInput` + `PublishSkillsetInput`; the
  field flows through the existing api/hooks passthrough untouched.

Part of #1155.
Let a skillset owner opt in to plugin export from the create/edit form:

- A checkbox (styled like the permissions Public toggle) after the tags row,
  seeded from `initial.exportAsPlugin`, included in BOTH the create and
  publish payloads.
- Disabled with a hint when the loaded skillset is not all-public —
  exporting member content is only safe then; the backend gates the actual
  export on all-public regardless.
- The edit page threads `exportAsPlugin` + `memberVisibilityState` into the
  form's initial state.
- New i18n strings (`exportLabel` / `exportHint` / `exportDisabledHint`) in
  both en + zh to satisfy the namespace-parity guard.

Part of #1155.
Skillset owners can opt in to exporting a skillset as one curated
multi-skill Claude Code plugin in the public mirror.
…1155)

Claude Code decides an installed plugin needs updating by a pure
string-equality compare on the version — there is no content hashing or
per-file change detection (verified against the CC docs). With the
plugin version pinned to only the skillset's published version, a moving
member ref (`A@latest`) that resolved to a new skill version rewrote the
bundled member files on the next sync but left the version string
unchanged, so CC kept the stale cached copy and the member update never
reached installed users.

Derive the plugin.json version as `<skillsetVersion>+sk<hash>`, where the
hash is over the sorted `name@version` of the resolved member set. The
fingerprint moves precisely when a member's resolved content changes (so
@latest updates propagate), stays byte-identical for pinned/unchanged
sets (no churn commit), and — unlike omitting the version to fall back on
a commit SHA — does not risk mass-updating every plugin on unrelated
syncs of the shared mirror repo. The fingerprint lives only in
plugin.json (which CC resolves first); the marketplace catalogue entry
keeps the plain owner version so the shared marketplace.json stays
identical across the full-reconcile and skill-incremental code paths.

Part of #1155.
…#1155)

Add a rail card to the skillset detail page that renders the Claude Code
install commands once the skillset is actually exported as a plugin. It
self-gates on the same conditions the mirror uses to export — owner
opt-in (`exportAsPlugin`) and `memberVisibilityState === "all-public"` —
plus the mirror being enabled, so it never advertises an install that
won't resolve.

Repo coordinates come from the existing public `GET /github/repo`
endpoint via `useGithubRepo` (the same source the skill install snippet
uses), so nothing is hardcoded and an admin re-point propagates here. The
card shows `/plugin marketplace add <owner>/<repo>` +
`/plugin install <name>@<repo>` with a copy button, and the one-time
"enable auto-update" reminder (third-party marketplaces default off). New
en + zh strings keep i18n parity.

Part of #1155.
…n-728d51

feat(api): serve a Claude Code marketplace from the public skill mirror
…-export

feat: export skillsets as curated multi-skill Claude Code plugins
Plugin export is now a deliberate, configurable action rather than a
create/publish side effect. Remove `exportAsPlugin` from the create +
publish schemas and their service threading, and add a dedicated
`PUT /skillsets/:id/plugin-export` endpoint.

The endpoint is owner/admin only (WRITE tier, like publish), rejects
enabling unless the skillset is `all-public` (the only state where
publishing member content to the public mirror is safe), and persists
the enabled flag plus optional listing overrides — `displayName`,
`description`, `keywords` — on a new `pluginConfig` field. Empty/omitted
overrides clear the stored config so the mirror falls back to the
skillset's own name/description/tags; the install name and the auto
fingerprint version stay non-editable. Fires the same fire-and-forget
mirror reconcile the other skillset mutations use.

Part of #1157.
Thread the owner's `pluginConfig` overrides through the plugin generator
so the exported `plugin.json` + marketplace entry honour them. plugin.json
gains an optional `displayName` (emitted only when set, in a stable key
position right after `name`, so per-skill plugins that never set it stay
byte-identical and never churn). The plugin/marketplace description falls
back to `pluginConfig.description ?? skillset.description` and keywords to
`pluginConfig.keywords ?? skillset.tags`. Both the full-reconcile subtree
path and the incremental marketplace-refresh path apply the same fallback
so the shared marketplace.json never drifts. Output stays deterministic.

Part of #1157.
…#1157)

The export opt-in no longer belongs in the create/edit form — it is now a
deliberate action on the detail page. Remove the checkbox, its
state/gating, the `exportAsPlugin` payload field, and the
`exportAsPlugin` / `memberVisibilityState` form-initial inputs, plus the
edit page's threading of them. Update the form tests to assert the
checkbox is gone and the create payload no longer carries the flag.

Part of #1157.
Add the `pluginConfig` overrides + `PluginExportInput` types, drop
`exportAsPlugin` from the create/publish input shapes (now set only via
the dedicated endpoint), add the `updatePluginExport` API call, and a
`useUpdatePluginExport` mutation that primes the detail cache and
invalidates the list tabs on success.

Part of #1157.
Add SkillsetPluginExportCard on the detail page, directly above the
visibility card. The owner clicks "Export as a Claude Code plugin"
(disabled with a hint when the skillset isn't all-public) to open a
confirm modal that customises the plugin's display name / description /
keywords, each prefilled from the skillset and overridable. Once
exported the card shows the install snippet plus "Edit fields" and
"Stop exporting" (behind a small confirm); any viewer sees the snippet
once exported. Absorbs the standalone SkillsetPluginInstallCard (and its
install-snippet rendering) and removes it.

Part of #1157.
Add the `skillsetPluginExport` namespace (card, modal, field labels,
status, install snippet, auto-update copy) to both en and zh locales and
the parity guard. Remove the now-orphaned `skillsetForm.export*` and
`skillsetDetail.pluginInstall*` keys left behind by the moved checkbox
and the absorbed install card.

Part of #1157.
…-export-card

feat: skillset plugin export as a detail-page card + confirm modal
Add `SkillsetRepository.findEligibleSkillsetsByMember(name, guid)` — the
export-eligible skillsets (`exportAsPlugin` + `memberVisibilityState:
all-public`) that reference a given member skill in any version. Backs the
targeted mirror re-export (#1159): on a member content / dist-tag change we
rebuild only the skillsets that actually reference the skill, not a full sweep.

It reuses the version repo's multikey `members` index
(`findSkillsetGuidsByMember`, matching by name OR guid across every ref
grammar), then narrows to the same eligibility predicate as
`findAllEligibleForMirror`. The all-public narrowing is load-bearing: a content
change must never leak a non-public skillset's member subtree to the mirror.

The version repo is now injected into the identity repo so the lookup can span
both collections; bootstrap wires them in dependency order.

Part of #1159.
Add `MirrorService.syncSkillsetsForMember(guid, name)` — rebuild only the
`skillsets/<name>/` subtrees of the export-eligible skillsets that reference a
changed member skill, in ONE commit, instead of a full reconcile per publish.
An exported skillset referencing the skill via `@latest`/`@tag` now reflects a
member change immediately rather than waiting up to 24h for the cron.

Mechanics:
- Extract `buildOneSkillsetSubtree(skillset)` from the reconcile sweep — the
  shared per-skillset assembly (resolve members, build the plugin, prefix the
  file map). The full reconcile now reuses it.
- Generalize the per-skill `commitSkillFolderChange` into `commitFolderSubtrees`
  (an arbitrary set of folder prefixes + a combined desired map). The per-skill
  publish/remove path is now a thin wrapper, so its behaviour is unchanged.
- The root marketplace.json is refreshed in the same commit but only stages a
  blob when its content actually changed, so a content-only member bump (the
  plain catalogue entry is unmoved) adds no churn.

reconcileInFlight: if a full reconcile is already running it rebuilds every
eligible subtree anyway, so the targeted sync defers to it rather than racing a
second commit against the same branch head. The cron remains the backstop.

Determinism is covered by a stateful-github test: a second identical sync
produces no commit. Eligibility narrowing (all-public + opted-in) lives in the
repo query, so a private member can never leak via this path.

Part of #1159.
Wire a fire-and-forget `fireSkillsetMirrorForMember(guid, name)` hook into the
skill CRUD routes and trigger it from every member-skill CONTENT change:

- `PUT /skills/:id` new-version publish — only when a ZIP is supplied (a pure
  privacy flip changes readability, not content, and is handled by the existing
  visibility-recompute hook → no double fire and no eligibility race).
- `POST /skills/:id/refresh` — a real refresh; not the dry-run preview.
- `PUT` / `DELETE /skills/:id/dist-tags/:tag` — a dist-tag move can re-resolve a
  `@tag`-pinned skillset member.

Bootstrap binds the hook to `MirrorService.syncSkillsetsForMember`, catching and
logging so a mirror hiccup never blocks the mutation response; the targeted
method already no-ops when the mirror is disabled or nothing is affected, and
the cron stays the backstop.

Part of #1159.
The skillset transfer-ownership route fired no mirror update — asymmetric with
the skill transfer path, which fires both hooks. A re-owned exported skillset
could keep stale provenance on the mirror for up to 24h. Add the existing
`fireMirrorReconcile?.()` after a successful transfer, matching the other
skillset mutation routes.

Part of #1159.
Minor bump — exported skillsets now re-export immediately on a member-skill
content / dist-tag change, instead of waiting for the nightly mirror reconcile.
feat(api): immediately re-export skillsets on member-skill changes
The skillset plugin builder now accepts an optional `excludedMembers` list
and renders a deterministic "Excluded members" section in the generated
README when it is non-empty, telling consumers which members were dropped
from the public bundle because their skill is currently private or
unresolvable.

The list is de-duped + sorted so the same exclusion set always renders
byte-identically (the mirror skips no-op commits by blob SHA, so any
non-determinism would manufacture churn). Excluded members are never part
of the bundled `members`, so the version fingerprint — folded only over the
included public subset — is unaffected by them.

Part of #1161.
Replace the all-public requirement for plugin export with a public-member
count, computed under SYSTEM (a member is public iff its skill resolves and
`isPrivate === false`).

- `setPluginExport` now rejects enabling unless the latest version has at
  least SKILLSET_MIN_PUBLIC_EXPORT_MEMBERS (2) public members, with a new
  `skillset_too_few_public_members` 409 code (replacing
  `skillset_not_all_public`). A "restricted" skillset with ≥2 public members
  may now export its public subset; disabling is always allowed.
- The skillset detail response gains `publicMemberCount` (over the returned
  version's members) so the web export card can gate on it and derive the
  excluded count.

The count is single-sourced with the mirror's export filter, so the gate,
the detail field, and what actually gets bundled always agree.

Part of #1161.
When exporting a skillset plugin, resolve each member under SYSTEM and
bundle only the public, resolvable members; private / unresolvable members
are dropped and passed to the README's excluded-members note. Nothing
private is ever fetched or written, so a private member's content can never
leak into the public mirror.

`buildOneSkillsetSubtree` returns null (drop the subtree) when fewer than
SKILLSET_MIN_PUBLIC_EXPORT_MEMBERS public members remain, so the reconcile
sweep removes the plugin. The targeted per-member re-export
(`syncSkillsetsForMember`) now also adds the folder prefix for an affected
skillset that fell below the floor, with no desired paths, so the shared
commit core deletes its stale `skillsets/<name>/` blobs. The marketplace
catalogue refresh resolves the public-member count too, so a dropped
skillset never lingers as a dangling `./skillsets/<name>` row.

Part of #1161.
Drop the `memberVisibilityState: "all-public"` filter from
`findAllEligibleForMirror` and `findEligibleSkillsetsByMember`; eligibility
is now `exportAsPlugin: true` alone. A "restricted" skillset (one private
member) is therefore returned to the mirror, which exports its public-member
subset; a member going private still surfaces its skillset to the targeted
re-export so that member is dropped (or the plugin removed when <2 public
remain). Only the public members' files are ever bundled, so nothing private
leaks. The compound `{exportAsPlugin, memberVisibilityState}` index still
serves the narrowed query via its `exportAsPlugin` prefix.

Part of #1161.
A skill permissions change flips which exported skillsets can bundle that
member, so the permissions route now also fires the targeted skillset
re-export (`fireSkillsetMirrorForMember`) alongside the existing
derived-visibility recompute. Going private drops the member from each
exported skillset's public subset (or removes the plugin when <2 public
remain); going public re-includes it — immediately, instead of waiting for
the nightly cron. The hook doc is updated to note it now fires on visibility
flips, not only content changes.

Part of #1161.
The plugin export card now gates the opt-in button on the new
`publicMemberCount` (≥2 public members) instead of
`memberVisibilityState === "all-public"`, so a restricted skillset that
still has enough public members can export its subset. The disabled hint
becomes "Requires at least 2 public member skills", the paused note reflects
the 2-public-member threshold, and an exported skillset with dropped members
shows "N member(s) excluded (private or unresolvable)". Adds
`publicMemberCount` to the `SkillsetDetail` type and the
SKILLSET_MIN_PUBLIC_EXPORT_MEMBERS constant, plus en/zh strings (parity).

Part of #1161.
…-export

feat: export the public-member subset of a skillset
Replace the owner-typed skillset version with a single, system-managed
revision in the same `<major>.<minor>` shape:

- Remove `version` from createSkillsetSchema / publishSkillsetSchema —
  create always starts at 1.0; every owner publish auto-bumps the minor
  (major never auto-bumps). Drops the old strict-increment guard.
- Each revision stores a lockfile-like `resolvedMembers` snapshot (the
  members resolved to concrete `name@<major.minor>` under SYSTEM), so a
  revision is reproducible even when its authored refs pin `@latest`.
- Add the reactive `bumpRevisionsForChangedMember`: for every skillset
  referencing a changed member skill (exported or not), re-cut the next
  minor ONLY when the resolved-member snapshot actually moved — a no-op
  member sync creates no revision (idempotency guard, no churn). The
  pointer advance skips the audit timestamps (member-driven, not an
  owner edit), mirroring the derived-visibility recompute.

Supporting plumbing: `resolvedMembers` on SkillsetVersionDocument + the
version repo (create/mapDoc/setResolvedMembers), `advanceLatestVersion`
on the identity repo, and `computeResolvedMembers` / a boot
`backfillResolvedMembers` in the recompute module.

Tests rewritten for the new contract: create → 1.0 with snapshot, owner
publish auto-bumps the minor + writes a fresh snapshot, and the reactive
bump cuts a revision only on a real member-version increase.

Part of #1162.
Sequence the reactive revision bump ahead of the targeted mirror
re-export in ONE fire-and-forget handler so an exported plugin always
picks up the freshly-bumped revision instead of racing two independent
hooks: `fireSkillsetMirrorForMember` now (1) awaits
`bumpRevisionsForChangedMember` for every referencing skillset whose
snapshot moved, then (2) calls `syncSkillsetsForMember`. Both steps
no-op cleanly (bump idempotent; export no-ops when the mirror is off /
nothing affected); the cron remains the backstop.

Also wire the one-shot `backfillResolvedMembers` boot backfill (after
the #1136 derived-visibility backfill) so pre-feature skillsets keep
their current `latestVersion` as the starting revision and gain a
resolved-member snapshot baseline for the reactive bump to diff against.

Part of #1162.
The exported plugin version is now the skillset revision itself, which
already advances when a member's resolved version moves (the service
bumps it before the mirror re-export). Delete `fingerprintVersion` and
its `+sk<hash>` suffix: `buildSkillsetPlugin` writes the plain revision
to both plugin.json and the marketplace entry, so the two always agree
and the no-op-commit skip is preserved (a revision unmoved → no diff).

Tests assert the plain revision (no fingerprint) on both surfaces.

Part of #1162.
…1162)

The skillset revision is now system-managed, so the create/publish form
no longer asks for one: remove the version input + the +minor/+major
bump controls and validation from SkillsetForm, and drop `version` from
the create/publish payload types and the edit page's seed. The detail
page relabels the metadata to "Revision" (read-only) with an
auto-managed hint.

i18n en + zh: drop the orphaned `skillsetForm.version*` keys, swap
`skillsetDetail.version` → `revision` + add `revisionAutoManaged`, and
refresh the edit hint. Tests assert no version field renders and that
create/publish omit `version`.

Part of #1162.
ornn-api + ornn-web minor: auto-incremented skillset revision replacing
the owner version + the +sk plugin fingerprint.
…evision

feat: auto-incremented skillset revision (retire user version + fingerprint)
)

The reactive revision bump (#1162) compared a snapshot of ALL resolved
member versions. A member going private is a VISIBILITY change, not a
version change — its resolved version is unchanged — so the all-members
snapshot stayed identical and no bump fired. But the EXPORTED (public-
resolvable) member set shrank. Since Claude Code detects plugin updates
ONLY by the version string, the exported plugin's content changed
(3 members → 2) while its version stayed the same, so installed clients
never picked up the update and kept the stale copy, including the
now-private member's files.

Make the snapshot the PUBLIC-resolvable member subset — exactly what the
mirror exports (#1161). Renamed `computeResolvedMembers` →
`computePublicResolvedMembers` and filtered to `node.isPrivate === false`,
so create / publish / backfill / reactive-bump all compare the same
public set. A visibility flip (private⇄public) now moves the snapshot and
bumps the minor, in addition to the existing version-increment trigger; an
unchanged public set still produces no bump (no churn).

The boot backfill keeps populating only docs that lack a snapshot and does
NOT rewrite a pre-fix all-members snapshot in place — that would desync the
stored revision from the export content with no version bump (the bug). A
doc still carrying such a snapshot self-corrects with one legitimate bump
on its first reactive member event, then is idempotent.

Bootstrap already sequences the bump BEFORE the targeted re-export, so the
re-exported plugin.json / marketplace entry carry the new revision.
…on (#1165)

End-to-end regression for the live bug: a member of a 3-member exported
skillset goes private. Asserts the targeted re-export (a) carries the
already-bumped skillset revision in plugin.json — the only signal Claude
Code uses to detect an update — and (b) drops the now-private member's
files while keeping the two public members (the set survives, above the
2-public-member export floor). Complements the service-level tests that
prove the bump itself fires on a visibility flip.
Skillset revision now bumps when a member is excluded (goes private) or
re-included (goes public) so Claude Code picks up the re-exported bundle.
…on-on-member-exclusion

fix: bump skillset revision when a member is excluded (goes private)
Add a third "via plugin (Claude Code Marketplace)" tab to the skill
detail install card, surfacing the Claude Code plugin install path that
the public mirror already publishes (#1153/#1154).

The tab shows the two `/plugin marketplace add <owner>/<repo>` +
`/plugin install <skill>@<repo>` commands, sourced from the same
useGithubRepo() coords + the skill's canonical name the npx tab uses,
followed by the shared auto-update-OFF note. It reuses the existing
CopyBlock + Tab switcher rather than introducing a new pattern.

Gating mirrors the npx tab: a public skill is mirrored as a single-skill
plugin, so the marketplace entry only exists when the skill is public
AND the mirror is enabled. Private skills get an "available once public"
hint; mirror-off deployments reuse the npx mirror-not-configured string —
neither renders a command that wouldn't resolve.

The auto-update note reuses the shared skillsetPluginExport.installAutoUpdate
key so the wording stays in lockstep with the skillset export card.
Pin the three plugin-tab states: public + mirror-on renders both
/plugin commands with the configured owner/repo + canonical skill name
and the auto-update note; a private skill shows the "available once
public" hint with no command block; a disabled mirror (and the
not-yet-loaded repo config) shows the mirror-not-configured treatment.

useGithubRepo is mocked at its seam via vi.hoisted so the test never
touches the apiClient / TanStack Query runtime.
…-install-tab

feat(web): 'via plugin (Claude Code Marketplace)' tab on the skill install card
The "via plugin (Claude Code Marketplace)" tab rendered its two commands
left-aligned with a below-box auto-update footer, making it taller and
asymmetric vs "via npx". Add a centered CopyBlock variant (whitespace-pre
so both command lines are kept) and use it for the plugin tab; fold the
auto-update reminder into the helper above the box so there's no per-tab
footer (#418 symmetry). The panel now matches "via npx" exactly.

Closes #1169
…yout

fix(web): center the skill plugin install tab like the npx tab
Dated release-notes file for the develop -> main release. Covers the Claude Code marketplace work: per-skill + skillset plugin export, the export card/modal, auto-managed skillset revisions, public-subset export, immediate mirror sync, and the install-command tabs. Empty changeset (docs-only).
…60630

docs: release notes for v0.15.0 (Claude Code marketplace)
@chronoai-shining
chronoai-shining merged commit 020189c into main Jun 30, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant