Merge pull request #836 from iflytek/feat/skillhub-cli-builtin-20260908

feat(skill): add first-party SkillHub CLI guide
This commit is contained in:
XiaoSeS 2026-09-09 17:29:56 +08:00 committed by GitHub
commit c36739ad16
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
34 changed files with 797 additions and 297 deletions

View file

@ -4,8 +4,8 @@ This directory contains the reviewed source used to build SkillHub's official st
packages. Each child of `skills/` is a complete package; generated ZIP files are release artifacts
and are not committed.
The first batch contains 15 general-purpose Skills covering study, office work, personal
productivity, content creation, weather, media, and frontend design. Every package includes:
The reviewed collection contains general-purpose Skills and focused operational Skills maintained
for SkillHub itself. Every package includes:
- a `SKILL.md` adapted for SkillHub;
- `LICENSE.txt` and `NOTICE.md` with pinned upstream provenance;
@ -25,8 +25,8 @@ added to the runtime manifest only after its immutable CDN URL is available; the
the matching SHA-256 so the backend can reject changed or incorrectly uploaded bytes before
extraction.
The first batch of 15 packages is pinned in the runtime manifest. A clean deployment initializes
these packages alongside the existing built-in Skills in the public `@global` namespace.
Every released package is pinned in the runtime manifest. A clean deployment initializes these
packages alongside the existing built-in Skills in the public `@global` namespace.
## Share a Skill with the Community

View file

@ -111,6 +111,16 @@
"path": "skills/memory-learning-science/retrieval-practice-generator"
}
},
{
"slug": "skillhub-cli",
"version": "2.0.0",
"license": "Apache-2.0",
"upstream": {
"repository": "https://github.com/iflytek/skillhub",
"commit": "42a0e423f4ac01e5e7e0801c786735cdd4a818cf",
"path": "web/src/docs/skill.md"
}
},
{
"slug": "storytelling-advisor",
"version": "1.0.0",

View file

@ -147,6 +147,28 @@
"Treating retrieval practice as a guaranteed learning result"
]
},
{
"slug": "skillhub-cli",
"prompt": "Connect this Codex Agent to https://skills.example.com and install @team-a/code-review version 2.1.0 from that SkillHub instance.",
"acceptance": [
"Uses only https://skills.example.com as the registry for the exact install",
"Falls back to https://skill.xfyun.cn only when no installed-metadata, explicit guide/request, environment, or CLI-config registry is available",
"Verifies the first-party SkillHub CLI identity before using a PATH command",
"Checks the live command help instead of assuming an undocumented flag is available",
"Replaces a recognized current-user-owned third-party skillhub launcher with the latest first-party global CLI while preserving unknown fields in shared SkillHub state files",
"Installs the latest @global/skillhub-cli without pinning a version, then installs @team-a/code-review version 2.1.0 for the Codex user scope with an explicit Agent target",
"Reports the registry, installed versions, Agent target, destination, integrity metadata, and observable Agent loading state"
],
"forbidden": [
"Substituting a similarly named Skill from another registry",
"Using an unrelated executable merely because it is named skillhub",
"Retaining an alias for the replaced third-party command, deleting an identity-unknown or system-managed executable, or deleting unknown fields from shared SkillHub state files",
"Using a per-operation npx fallback, an undocumented flag, or raw HTTP as a substitute for the first-party global CLI",
"Requesting a token in chat or exposing credentials in output",
"Using --force or changing the user's default registry without approval",
"Claiming that file installation proves the current Agent session loaded the Skill or inventing a universal activation command"
]
},
{
"slug": "storytelling-advisor",
"prompt": "Help shape this true customer story: a small clinic reduced morning phone queues after adding online booking. I have no verified numbers or customer names.",

View file

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets.) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2026 iFlytek Co., Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -0,0 +1,19 @@
# Source notice
- Source project: `iflytek/skillhub`
- Source repository: <https://github.com/iflytek/skillhub>
- Fixed revision: `42a0e423f4ac01e5e7e0801c786735cdd4a818cf`
- Source path: `web/src/docs/skill.md`
- License: Apache-2.0; see `LICENSE.txt`
## SkillHub modifications
SkillHub adaptation version: `2.0.0`.
- Created a dedicated first-party CLI Skill instead of changing the existing ClawHub-oriented `skillhub-registry` Skill.
- Separated anonymous bootstrap guidance from the persistent Agent installation while keeping one instruction body.
- Added CLI identity checks to avoid invoking an unrelated executable with the same name.
- Added live-help verification and a reviewed operations reference for sync, publish, removal, repair, and troubleshooting.
- Added POSIX and PowerShell 7 credential-entry guidance without placing tokens in command history.
- Preserved exact registry, coordinate, version, Agent target, authentication, and integrity boundaries.
- Removed automatic public-registry fallback for exact installs and private discovery queries.

View file

@ -0,0 +1,168 @@
---
name: skillhub-cli
description: Connect an Agent to a SkillHub registry and use the official SkillHub CLI to search, install, list, or explicitly upgrade SkillHub skills. Use when a user asks to connect SkillHub, install a SkillHub skill, or manage skills previously installed from SkillHub.
version: 2.0.0
license: Apache-2.0
---
# SkillHub CLI
Use the registry that supplied this guide to connect the current Agent and manage SkillHub packages with the first-party `@astron-team/skillhub` CLI.
## Resolve The Registry
Resolve `<registry>` once before composing commands. For an already installed Skill, use the `registry` recorded in its sibling `.skillhub/metadata.json`; that source is authoritative for later searches and upgrades. Otherwise resolve in this order:
1. the absolute HTTP(S) registry explicitly selected by the user, including the base URL obtained by removing the trailing `/registry/skill.md` from the URL used to fetch this guide;
2. `SKILLHUB_REGISTRY`;
3. the `registry` field in `~/.skillhub/config.json`;
4. `https://skill.xfyun.cn`.
Use only an absolute HTTP(S) URL. Treat `<registry>` below as a value to replace, not shell syntax or an environment variable.
Keep the exact registry selected by the user for the current request. Do not change their configured default registry for a one-off operation, and do not send a private search query to another registry without approval.
## Use The First-Party CLI
First check whether the command on `PATH` is the expected CLI:
```bash
skillhub version
```
Use it only when the output is `SkillHub CLI <version>`. A different result may be an unrelated command with the same name.
When connecting this registry, install the latest first-party CLI globally so future manual `skillhub` commands use this implementation:
```console
npm install --global @astron-team/skillhub
skillhub version
```
If `skillhub version` still resolves to a known third-party launcher after installation, locate the exact command selected by the shell, remove that conflicting launcher only when it is owned by the current user, refresh command lookup, and run the global installation again. Do not retain or create an alias for the replaced command. Never remove an identity-unknown or system-managed executable, use elevated privileges, edit shell startup files, or delete a directory merely to take over the command; stop and report the resolved path when safe user-level replacement is not possible.
Replacing the executable must not replace the other tool's data. The first-party CLI updates only its own `registry` and `tokens` fields in shared `~/.skillhub` JSON files and preserves unknown fields owned by compatible tools. Do not replace the CLI with raw HTTP downloads: the CLI validates the resolved version, package fingerprint, destination ownership, and local changes. Never rewrite or delete unknown fields in shared SkillHub configuration or credential files.
Before using an operation or flag not shown in this Skill, inspect both live help surfaces for the selected CLI:
```bash
skillhub help <command>
skillhub <command> --help
```
Repository documentation may describe unreleased behavior. If neither live help surface exposes a proposed command or flag, do not use it. Require Node.js 18 or newer when using the npm package.
## Choose The Flow
- **Connect SkillHub:** ensure `@global/skillhub-cli` is installed for the current Agent at user scope, then continue the requested operation.
- **Install an exact Skill:** install the requested coordinate and version directly from this registry; do not search for or substitute a similarly named package.
- **Discover a Skill:** search this registry first. If it is unavailable or has no suitable result, report that outcome and ask before querying another registry.
- **Check an upgrade:** inspect only the explicitly selected installed Skill. Never upgrade every installation implicitly.
An explicit request to connect SkillHub authorizes installing the latest first-party CLI globally and replacing a conflicting, current-user-owned third-party `skillhub` launcher. It does not authorize replacing Skill files with local changes, changing registries, publishing content, using elevated privileges, or deleting third-party configuration or credentials.
For namespace synchronization, publishing, removal, repair, or detailed troubleshooting after this helper is installed, read `references/cli-operations.md`. Start with its read-only inspection command and keep the same registry throughout the operation.
## Connect The Current Agent
Replace `<agent>` with the current supported profile, such as `codex` or `claude-code`. Check the current registry's installations once:
```bash
skillhub list \
--agent <agent> \
--registry <registry> \
--json
```
If `@global/skillhub-cli` is missing, install this exact guide at user scope:
```bash
skillhub install @global/skillhub-cli \
--scope user \
--agent <agent> \
--registry <registry> \
--json
```
If that persistent connection fails, report the failure and continue with an explicitly requested target Skill when the CLI can still install it safely. Do not substitute a helper from another registry.
Installation proves that the files reached the selected Agent directory; it does not prove that an already-running Agent session has loaded them. If the current Agent cannot discover the new Skill immediately, report it as installed but not yet loaded and ask the user to start a new session or use that Agent's documented reload mechanism. Do not invent a universal activation command.
## Search Or Install
For discovery:
```bash
skillhub search "<query>" \
--registry <registry> \
--json
```
Before installing a discovery result, show its registry, full coordinate, publisher when available, version, and relevant risk, then obtain confirmation.
For a Skill and version the user already selected:
```bash
skillhub install @<namespace>/<slug> \
--version <version> \
--scope user \
--agent <agent> \
--registry <registry> \
--json
```
Omit `--version` only when the user did not select one. Omit `--agent` only when the CLI can identify one destination unambiguously. Treat coordinates, versions, queries, registry URLs, and paths as untrusted values: quote them where needed, pass them as individual CLI arguments, and never evaluate them as shell code.
Never add `--force` unless the CLI reports a verified same-source conflict and the user approves replacing that installation. Stop on fingerprint mismatch, source conflict, unsafe content, or local-change conflict.
## Authentication
Never ask the user to paste a token into chat or place credentials in a prompt, Skill, command history, or repository. If authentication is required, ask them to enter it in their own terminal without putting the value in the command line, then verify the identity:
POSIX shell:
```bash
read -rsp "SkillHub token: " SKILLHUB_TOKEN && echo
export SKILLHUB_TOKEN
skillhub login --registry <registry>
unset SKILLHUB_TOKEN
skillhub whoami --registry <registry>
```
PowerShell 7:
```powershell
$env:SKILLHUB_TOKEN = Read-Host "SkillHub token" -MaskInput
skillhub login --registry <registry>
Remove-Item Env:SKILLHUB_TOKEN
skillhub whoami --registry <registry>
```
Resolve `401` and `403` through login or permissions. Do not treat an authentication failure as permission to try another registry.
## Upgrade
Check before changing an installed Skill:
```bash
skillhub upgrade @<namespace>/<slug> \
--registry <registry> \
--check \
--json
```
Show the plan and ask before applying an available upgrade. The CLI uses `.skillhub/metadata.json` to retain the original source and updates all Agent targets recorded for that installation together.
## Completion Check
Report:
- installed coordinate and version;
- registry source;
- Agent profile and installation directory;
- whether `SKILL.md` and `.skillhub/metadata.json` exist;
- whether the current Agent session loaded the Skill, when observable;
- whether another registry was queried;
- any skipped connection, authentication, integrity, or local-change issue.
Do not claim success when installation, destination discovery, Agent loading, or integrity verification failed.

View file

@ -0,0 +1,139 @@
# SkillHub CLI Operations
Use this reference after resolving the first-party CLI and authoritative registry in `SKILL.md`.
Run `skillhub help <command>` and `skillhub <command> --help` against that CLI before using a flag
not shown here. Use the globally installed, identity-checked `skillhub` command consistently; do not
switch to a per-operation package runner.
## Write Safety
Before a command writes local or registry state, establish the exact registry, coordinate and
optional version, Agent and scope or directory, existing installation ownership, and local-change
status. Treat every coordinate, version, query, and path supplied by a user as one quoted argument.
Start with the read-only operation in this table. Obtain explicit approval before the corresponding
write unless the user's current request already names that exact action and target.
| Task | Inspect first | Write |
|---|---|---|
| Install | `search`, `list` | `install` |
| Upgrade | `list`, `upgrade --check` | `upgrade` |
| Namespace sync | `sync status`, `sync diff`, `sync pull --check` | selected `sync pull` or `sync push` |
| Publish | inspect package, `publish --dry-run` | `publish` |
| Remove | `list` | precise `remove` |
Treat a current request that names the exact action and target as approval for that action. Otherwise,
obtain approval before `--force`, `--prune`, `remove --all`, remote removal, `--hard`, `logout`, or
`doctor`. Do not choose a commit, backup, deletion, or discard strategy when local changes block an
operation.
## Coordinates And Destinations
Accepted coordinates include `slug`, `namespace/slug`, `@namespace/slug`, and
`namespace--slug`. A bare slug resolves to `global` unless `--namespace` selects another namespace.
Use a full coordinate when known.
Use an Agent profile reported by live help and repeat `--agent` for multiple targets. For an
unsupported Agent, use an absolute `--dir` selected by the user. Do not combine `--dir` with
`--scope` or `--agent`.
After installation, run `list` with the same registry and Agent filter. Confirm the installed
version and that both `SKILL.md` and `.skillhub/metadata.json` exist.
## Upgrade An Installed Skill
Upgrade only explicitly named, SkillHub-managed installations. There is no implicit upgrade-all:
```bash
skillhub list --registry <registry> --json
skillhub upgrade '@team/code-review' --registry <registry> --check --json
skillhub upgrade '@team/code-review' --registry <registry>
```
Show the check plan before writing. Without approved `--force`, local changes block replacement.
Never bypass a downgrade, source conflict, unmanaged directory, fingerprint mismatch, or partial
target selection that cannot preserve one shared version.
## Synchronize A Namespace Workspace
Use `sync` only for an authenticated, non-`global` namespace. Inspect before pulling:
```bash
skillhub sync status --namespace team-a --dir <skills-dir> --registry <registry> --json
skillhub sync diff --namespace team-a --dir <skills-dir> --registry <registry>
skillhub sync pull --namespace team-a --dir <skills-dir> --registry <registry> --check
```
Outside an interactive terminal, select every write explicitly:
```bash
skillhub sync pull --namespace team-a \
--skill code-review \
--dir <skills-dir> \
--registry <registry>
```
An empty interactive selection changes nothing. Do not add `--force` for local changes or `--prune`
for orphaned Skills without approval for the exact affected paths.
Before upload, validate without creating a version:
```bash
skillhub sync push --all \
--namespace team-a \
--dir <skills-dir> \
--registry <registry> \
--dry-run
```
Only add `--submit-review` after validation and confirmation. A submission may return `SCANNING`,
`UPLOADED`, `PENDING_REVIEW`, or `PUBLISHED`; only `PUBLISHED` proves immediate installability.
## Publish A Skill
Inspect the package and require a root-level `SKILL.md`. Validate against the selected registry:
```bash
skillhub publish ./my-skill \
--namespace team-a \
--visibility public \
--registry <registry> \
--dry-run
```
`--dry-run` sends the package bytes to the selected registry for validation. Obtain approval before
sending a local or private package that the user has not already asked to validate or publish. Fix
validation errors instead of forcing publication. Before repeating without `--dry-run`, confirm
the resolved namespace, slug, version, visibility, and included files. Report the returned lifecycle
status; a successful submission is not necessarily published.
## Remove Or Repair
List first, then use a full coordinate and the narrowest target filter:
```bash
skillhub list --agent codex --registry <registry>
skillhub remove '@team/code-review' --agent codex --registry <registry>
```
A bare-slug removal can match same-slug installations in multiple namespaces. Remote removal is
destructive: confirm the exact registry, namespace, and slug. `--hard` only suppresses an interactive
prompt; it never grants permission.
Use `skillhub doctor` to rebuild inventory after manual damage or stale records. Review its result
and retained backup. It does not resolve conflicting installed versions for the user.
## Troubleshoot
| Symptom | Check |
|---|---|
| Unknown command or option | Check CLI identity and both live help surfaces; update only with approval. |
| Authentication failure | Confirm registry, run `whoami`, and have the user refresh credentials privately. |
| Wrong installation directory | Inspect `list --json`; reinstall only after choosing explicit scope, Agent, or directory. |
| Install or upgrade blocked | Preserve files; inspect source ownership, metadata, version direction, and local changes. |
| Publish validation failed | Fix the reported package, metadata, permission, or scanner issue. |
| Inventory stale | Run `doctor`, review its result, and keep its backup. |
| Registry error | Preserve the public message and `requestId`; do not guess the server-side cause. |
Report the registry, coordinate and version, Agent, scope or directory, preview performed, files
changed, and verification result. For publish and sync push, report the actual lifecycle status.

View file

@ -260,10 +260,10 @@ installs a missing Skill and has no implicit upgrade-all mode.
```bash
# Preview without changing files
skillhub upgrade @global/skillhub-registry --check
skillhub upgrade @global/skillhub-cli --check
# Upgrade one or a bounded list of installed Skills
skillhub upgrade @global/skillhub-registry
skillhub upgrade @global/skillhub-cli
skillhub upgrade @team/code-review @team/java-guide
# Machine-readable plan

View file

@ -35,7 +35,7 @@ export const commands = {
},
install: {
summary: 'Install a skill locally',
usage: 'skillhub install <coordinate> [--scope <user|project>] [--namespace <slug>] [--version <v>] [--agent <profile>] [--dir <path>] [--force] [--json]',
usage: 'skillhub install <coordinate> [--scope <user|project>] [--namespace <slug>] [--version <v>] [--agent <profile>] [--dir <path>] [--force] [--registry <url>] [--token <token>] [--json]',
examples: [
'skillhub install pdf-parser',
'skillhub install team/my-skill',
@ -47,9 +47,9 @@ export const commands = {
},
upgrade: {
summary: 'Upgrade explicitly selected installed skills',
usage: 'skillhub upgrade <coordinate...> [--namespace <slug>] [--agent <profile>] [--dir <path>] [--registry <url>] [--check] [--force] [--json]',
usage: 'skillhub upgrade <coordinate...> [--namespace <slug>] [--agent <profile>] [--dir <path>] [--registry <url>] [--token <token>] [--check] [--force] [--json]',
examples: [
'skillhub upgrade @global/skillhub-registry',
'skillhub upgrade @global/skillhub-cli',
'skillhub upgrade @team/code-review @team/java-guide --check --json',
'skillhub upgrade code-review --namespace team --agent codex'
]
@ -70,7 +70,7 @@ export const commands = {
},
remove: {
summary: 'Remove local or remote skill',
usage: 'skillhub remove <coordinate> [--agent <profile>] [--all] [--remote] [--hard] [--namespace <slug>] [--json]',
usage: 'skillhub remove <coordinate> [--agent <profile>] [--all] [--remote] [--hard] [--namespace <slug>] [--registry <url>] [--token <token>] [--json]',
examples: [
'skillhub remove pdf-parser',
'skillhub remove team/my-skill',
@ -85,7 +85,7 @@ export const commands = {
},
publish: {
summary: 'Publish a local skill package',
usage: 'skillhub publish <path> [--namespace <slug>] [--visibility <public|namespace-only|private>] [--registry <url>] [--json]',
usage: 'skillhub publish <path> [--namespace <slug>] [--visibility <public|namespace-only|private>] [--dry-run] [--registry <url>] [--token <token>] [--json]',
examples: ['skillhub publish ./my-skill', 'skillhub publish ./my-skill --namespace myspace']
},
update: {

View file

@ -7,6 +7,8 @@ describe('help command', () => {
expect(result.exitCode).toBe(0)
expect(result.stdout).toContain('Usage: skillhub install <coordinate>')
expect(result.stdout).toContain('--agent <profile>')
expect(result.stdout).toContain('--version <v>')
expect(result.stdout).toContain('--registry <url>')
expect(result.stdout).toContain('@team/my-skill')
expect(result.stdout).toContain('team/my-skill')
expect(result.stdout).toContain('team--my-skill')
@ -18,6 +20,7 @@ describe('help command', () => {
expect(result.stdout).toContain('Usage: skillhub remove <coordinate>')
expect(result.stdout).toContain('skillhub remove team/my-skill')
expect(result.stdout).toContain('skillhub remove my-skill --namespace team')
expect(result.stdout).toContain('--registry <url>')
})
test('prints namespaced local remove contract in --help', async () => {
@ -51,6 +54,11 @@ describe('help command', () => {
expect(sync.stdout).toContain('namespace workspaces')
expect(sync.stdout).toContain('--namespace <slug>')
expect(sync.stdout).toContain('--skill <slug>')
const publish = await runCli(['help', 'publish'])
expect(publish.exitCode).toBe(0)
expect(publish.stdout).toContain('--dry-run')
expect(publish.stdout).toContain('--registry <url>')
})
// P1: bare `skillhub help` (no topic) prints the directory of all commands

View file

@ -241,10 +241,10 @@ ZIP, and `.skillhub/` is excluded when an installed directory is published again
```bash
# Preview without changing files
skillhub upgrade @global/skillhub-registry --check
skillhub upgrade @global/skillhub-cli --check
# Upgrade one or a bounded list of installed Skills
skillhub upgrade @global/skillhub-registry
skillhub upgrade @global/skillhub-cli
skillhub upgrade @team/code-review @team/java-guide
# Deterministic machine-readable plan

View file

@ -18,6 +18,26 @@ python3 "$BUILDER" --output "$second"
cmp "$first/artifacts.json" "$second/artifacts.json"
# The anonymous Agent bootstrap route and the installable helper Skill share
# one reviewed instruction body. The web copy exists only because its Docker
# build context is intentionally limited to web/.
cmp \
"$REPO_ROOT/builtin-skills/skills/skillhub-cli/SKILL.md" \
"$REPO_ROOT/web/src/docs/skill.md.template"
test -f "$REPO_ROOT/builtin-skills/skills/skillhub-cli/references/cli-operations.md"
grep -F 'npm install --global @astron-team/skillhub' \
"$REPO_ROOT/builtin-skills/skills/skillhub-cli/SKILL.md" >/dev/null
grep -F 'version: 2.0.0' \
"$REPO_ROOT/builtin-skills/skills/skillhub-cli/SKILL.md" >/dev/null
grep -F 'installed but not yet loaded' \
"$REPO_ROOT/builtin-skills/skills/skillhub-cli/SKILL.md" >/dev/null
grep -F 'skillhub sync pull --namespace team-a' \
"$REPO_ROOT/builtin-skills/skills/skillhub-cli/references/cli-operations.md" >/dev/null
grep -F 'skillhub publish ./my-skill' \
"$REPO_ROOT/builtin-skills/skills/skillhub-cli/references/cli-operations.md" >/dev/null
grep -F '`--dry-run` sends the package bytes to the selected registry' \
"$REPO_ROOT/builtin-skills/skills/skillhub-cli/references/cli-operations.md" >/dev/null
runtime_manifest="$REPO_ROOT/server/skillhub-app/src/main/resources/builtin-skills/manifest.json"
python3 - "$first/artifacts.json" "$runtime_manifest" <<'PY'
import json

View file

@ -150,7 +150,7 @@ expect_fail "$missing_trailing_base_env" "must be '/' or start and end with '/'"
# A base path whose first segment collides with a server Nginx location (/api/,
# /oauth2/, ...) must be rejected: it would shadow the real route and break the app.
for reserved in /api/ /oauth2/ /login/ /assets/ /install/ /registry/ /nginx-health/ /.well-known/ /runtime-config.js/ /api/nested/; do
for reserved in /api/ /oauth2/ /login/ /assets/ /registry/ /nginx-health/ /.well-known/ /runtime-config.js/ /api/nested/; do
reserved_base_env="$tmp/reserved-base.env"
write_env "$reserved_base_env" "release-download-secret-32-bytes-minimum"
printf 'SKILLHUB_WEB_BASE_PATH=%s\n' "$reserved" >>"$reserved_base_env"

View file

@ -25,7 +25,7 @@ cleanup() {
trap cleanup EXIT
html="$tmp/html"
mkdir -p "$html/assets" "$html/install" "$html/registry"
mkdir -p "$html/assets" "$html/registry"
printf '%s\n' 'INDEX_HTML_MARKER' >"$html/index.html"
printf '%s\n' 'APP_JS_MARKER' >"$html/assets/app.js"
cp "$ROOT_DIR/web/src/docs/skill.md.template" "$html/registry/skill.md.template"
@ -107,36 +107,26 @@ if [ "$location" != '/skillhub/' ]; then
exit 1
fi
# The preferred Agent install guide is generated from the instance URL and is
# reachable through the configured sub-path. The legacy registry route remains
# available from the same source document.
guide=$(curl -fsS "$base/skillhub/install/skillhub.md")
printf '%s' "$guide" | grep -F 'The primary registry for this guide is `https://skill.example.com/skillhub`.' >/dev/null
printf '%s' "$guide" | grep -F 'read the sibling `.skillhub/metadata.json` first' >/dev/null
printf '%s' "$guide" | grep -F 'skillhub list --agent <agent> --registry https://skill.example.com/skillhub --json' >/dev/null
printf '%s' "$guide" | grep -F 'skillhub install @global/skillhub-registry' >/dev/null
printf '%s' "$guide" | grep -F 'skillhub upgrade @global/skillhub-registry \' >/dev/null
if printf '%s' "$guide" | sed -n '/skillhub upgrade @global\/skillhub-registry \\/,/--json/p' | grep -F -- '--agent' >/dev/null; then
echo 'helper upgrade must cover all installed Agent targets' >&2
exit 1
fi
printf '%s' "$guide" | grep -F 'skillhub search "<query>" --registry https://skill.xfyun.cn --json' >/dev/null
printf '%s' "$guide" | grep -F 'npx --yes clawhub search "<query>"' >/dev/null
printf '%s' "$guide" | grep -F 'skillhub login --token <token> --registry https://skill.example.com/skillhub' >/dev/null
legacy_guide=$(curl -fsS "$base/skillhub/registry/skill.md")
if [ "$guide" != "$legacy_guide" ]; then
echo 'preferred and compatibility Agent guides must have identical content' >&2
exit 1
fi
cache_control=$(curl -sSI "$base/skillhub/install/skillhub.md" | awk -F': ' 'tolower($1) == "cache-control" { print $2 }' | tr -d '\r')
# The Agent bootstrap guide derives a configured registry from the URL used to
# fetch it and is reachable through its single route under the configured path.
guide=$(curl -fsS "$base/skillhub/registry/skill.md")
printf '%s' "$guide" | grep -F 'removing the trailing `/registry/skill.md` from the URL used to fetch this guide' >/dev/null
printf '%s' "$guide" | grep -F '4. `https://skill.xfyun.cn`.' >/dev/null
printf '%s' "$guide" | grep -F 'sibling `.skillhub/metadata.json`' >/dev/null
printf '%s' "$guide" | grep -F ' --registry <registry> \' >/dev/null
printf '%s' "$guide" | grep -F 'skillhub install @global/skillhub-cli' >/dev/null
printf '%s' "$guide" | grep -F 'npm install --global @astron-team/skillhub' >/dev/null
printf '%s' "$guide" | grep -F 'ask before querying another registry' >/dev/null
printf '%s' "$guide" | grep -F 'skillhub login --registry <registry>' >/dev/null
cache_control=$(curl -sSI "$base/skillhub/registry/skill.md" | awk -F': ' 'tolower($1) == "cache-control" { print $2 }' | tr -d '\r')
if [ "$cache_control" != 'no-cache' ]; then
echo "Agent guide must be revalidated instead of cached indefinitely, got: $cache_control" >&2
exit 1
fi
# An explicit URL is authoritative and must not interpolate a hostile request Host.
explicit_hostile=$(curl -fsS -H 'Host: evil.example;echo_injected' "$base/skillhub/install/skillhub.md")
printf '%s' "$explicit_hostile" | grep -F 'The primary registry for this guide is `https://skill.example.com/skillhub`.' >/dev/null
explicit_hostile=$(curl -fsS -H 'Host: evil.example;echo_injected' "$base/skillhub/registry/skill.md")
printf '%s' "$explicit_hostile" | grep -F '4. `https://skill.xfyun.cn`.' >/dev/null
if printf '%s' "$explicit_hostile" | grep -F 'echo_injected' >/dev/null; then
echo 'explicit Agent guide must not interpolate the request Host' >&2
exit 1
@ -147,7 +137,7 @@ docker rm -f "$name" >/dev/null 2>&1 || true
# With no explicit public URL, the guide must derive the registry from the
# sanitized request scheme, Host (including port), and deployment base path.
default_html="$tmp/default-html"
mkdir -p "$default_html/assets" "$default_html/install" "$default_html/registry"
mkdir -p "$default_html/assets" "$default_html/registry"
printf '%s\n' 'INDEX_HTML_MARKER' >"$default_html/index.html"
cp "$ROOT_DIR/web/src/docs/skill.md.template" "$default_html/registry/skill.md.template"
cp "$ROOT_DIR/web/runtime-config.js.template" "$default_html/runtime-config.js.template"
@ -175,16 +165,16 @@ until curl -fsS -o /dev/null "$default_base/nginx-health" 2>/dev/null; do
fi
sleep 1
done
default_guide=$(curl -fsS "$default_base/skillhub/install/skillhub.md")
printf '%s' "$default_guide" | grep -F "The primary registry for this guide is \`$default_base/skillhub\`." >/dev/null
untrusted_https=$(curl -fsS -H 'X-Forwarded-Proto: https' "$default_base/skillhub/install/skillhub.md")
printf '%s' "$untrusted_https" | grep -F "The primary registry for this guide is \`$default_base/skillhub\`." >/dev/null
default_guide=$(curl -fsS "$default_base/skillhub/registry/skill.md")
printf '%s' "$default_guide" | grep -F 'removing the trailing `/registry/skill.md` from the URL used to fetch this guide' >/dev/null
untrusted_https=$(curl -fsS -H 'X-Forwarded-Proto: https' "$default_base/skillhub/registry/skill.md")
printf '%s' "$untrusted_https" | grep -F '4. `https://skill.xfyun.cn`.' >/dev/null
if printf '%s' "$default_guide" | grep -F '__SKILLHUB_PUBLIC_BASE_URL__' >/dev/null; then
echo 'default Agent guide must not expose the runtime URL marker' >&2
exit 1
fi
for hostile_host in 'evil.example;echo_injected' 'evil.example$(id)' 'evil.example&whoami'; do
hostile_status=$(curl -sS -o "$tmp/hostile-response" -w '%{http_code}' -H "Host: $hostile_host" "$default_base/skillhub/install/skillhub.md")
hostile_status=$(curl -sS -o "$tmp/hostile-response" -w '%{http_code}' -H "Host: $hostile_host" "$default_base/skillhub/registry/skill.md")
if [ "$hostile_status" != 400 ]; then
echo "dynamic Agent guide must reject hostile Host, got $hostile_status for $hostile_host" >&2
exit 1
@ -199,7 +189,7 @@ docker rm -f "$name_default" >/dev/null 2>&1 || true
# A trusted proxy may supply one exact canonical scheme. Comma-separated or
# otherwise malformed values retain the direct request scheme.
trusted_html="$tmp/trusted-html"
mkdir -p "$trusted_html/assets" "$trusted_html/install" "$trusted_html/registry"
mkdir -p "$trusted_html/assets" "$trusted_html/registry"
printf '%s\n' 'INDEX_HTML_MARKER' >"$trusted_html/index.html"
cp "$ROOT_DIR/web/src/docs/skill.md.template" "$trusted_html/registry/skill.md.template"
cp "$ROOT_DIR/web/runtime-config.js.template" "$trusted_html/runtime-config.js.template"
@ -226,10 +216,10 @@ until curl -fsS -o /dev/null "$trusted_base/nginx-health" 2>/dev/null; do
fi
sleep 1
done
trusted_https=$(curl -fsS -H 'X-Forwarded-Proto: https' "$trusted_base/skillhub/install/skillhub.md")
printf '%s' "$trusted_https" | grep -F "The primary registry for this guide is \`https://127.0.0.1:$port_trusted/skillhub\`." >/dev/null
trusted_malformed=$(curl -fsS -H 'X-Forwarded-Proto: https,http' "$trusted_base/skillhub/install/skillhub.md")
printf '%s' "$trusted_malformed" | grep -F "The primary registry for this guide is \`$trusted_base/skillhub\`." >/dev/null
trusted_https=$(curl -fsS -H 'X-Forwarded-Proto: https' "$trusted_base/skillhub/registry/skill.md")
printf '%s' "$trusted_https" | grep -F 'removing the trailing `/registry/skill.md` from the URL used to fetch this guide' >/dev/null
trusted_malformed=$(curl -fsS -H 'X-Forwarded-Proto: https,http' "$trusted_base/skillhub/registry/skill.md")
printf '%s' "$trusted_malformed" | grep -F '4. `https://skill.xfyun.cn`.' >/dev/null
docker rm -f "$name_trusted" >/dev/null 2>&1 || true
# Fixed-base image served via the bundled deploy configs: assets are baked under

View file

@ -67,7 +67,7 @@ grep -F '/assets/index.js' "$root_web_root/index.html" >/dev/null
# would desync the generated location from the baked asset URLs).
for bad in '/foo/../bar/' '/foo/./bar/' '/foo//bar/' '/no-trailing' 'foo/' \
'/api/' '/oauth2/' '/login/' '/assets/' '/registry/' '/nginx-health/' \
'/install/' '/.well-known/' '/runtime-config.js/' '/api/nested/'; do
'/.well-known/' '/runtime-config.js/' '/api/nested/'; do
reject_root="$tmp/reject-html"
reject_config="$tmp/reject.conf"
mkdir -p "$reject_root"

View file

@ -139,7 +139,7 @@ validate_web_base_path_format() {
first_segment=${value#/}
first_segment=${first_segment%%/*}
case "$first_segment" in
api|oauth2|login|assets|install|registry|nginx-health|.well-known|runtime-config.js)
api|oauth2|login|assets|registry|nginx-health|.well-known|runtime-config.js)
error "SKILLHUB_WEB_BASE_PATH must not start with a segment reserved by the SkillHub server ($first_segment); it would shadow the server's own Nginx location: $value"
;;
esac

View file

@ -78,6 +78,12 @@
"url": "https://bjcdn.openstorage.cn/open_res/xfyundoc/2026-07-31/ac9086e8-ee32-4baa-bf42-0bc3f5c59558/skillhub-builtin-skills/retrieval-practice-generator/1.0.0/8e1b21f9c02312378da4d5493297d98a2ce40ba03f83a51e24c97ad53f144dc1.zip",
"sha256": "8e1b21f9c02312378da4d5493297d98a2ce40ba03f83a51e24c97ad53f144dc1"
},
{
"slug": "skillhub-cli",
"version": "2.0.0",
"url": "https://bjcdn.openstorage.cn/open_res/xfyundoc/2026-09-09/675e2a82-2361-4b2b-bf61-fab81be6db3f/1788943688653/bb3df7fbea91d40c562cc8e303b2c680b27651a853b4b60f49febefd66d0bfa9.zip",
"sha256": "bb3df7fbea91d40c562cc8e303b2c680b27651a853b4b60f49febefd66d0bfa9"
},
{
"slug": "storytelling-advisor",
"version": "1.0.0",

View file

@ -27,7 +27,6 @@ describe('validateBasePath', () => {
'/oauth2/',
'/login/',
'/assets/',
'/install/',
'/registry/',
'/nginx-health/',
'/.well-known/',

View file

@ -9,7 +9,6 @@ const RESERVED_FIRST_SEGMENTS = new Set([
'oauth2',
'login',
'assets',
'install',
'registry',
'nginx-health',
'.well-known',

View file

@ -55,7 +55,7 @@ case "$SKILLHUB_WEB_BASE_PATH" in
esac
# Reject base paths whose first segment is reserved by the server's own Nginx
# locations (/api/, /oauth2/, /login/, /assets/, /install/, /registry/, /nginx-health,
# locations (/api/, /oauth2/, /login/, /assets/, /registry/, /nginx-health,
# /.well-known/, /runtime-config.js). Generating `location ^~ /api/` would
# shadow the real API route and take down the whole app. Kept in sync with
# web/base-path-config.ts, validate-release-config.sh and the Helm checks.
@ -63,7 +63,7 @@ if [ "$SKILLHUB_WEB_BASE_PATH" != / ]; then
first_segment=${SKILLHUB_WEB_BASE_PATH#/}
first_segment=${first_segment%%/*}
case "$first_segment" in
api|oauth2|login|assets|install|registry|nginx-health|.well-known|runtime-config.js)
api|oauth2|login|assets|registry|nginx-health|.well-known|runtime-config.js)
echo "SKILLHUB_WEB_BASE_PATH must not start with a segment reserved by the SkillHub server ($first_segment); it would shadow the server's own Nginx location: $SKILLHUB_WEB_BASE_PATH" >&2
exit 1
;;

View file

@ -21,9 +21,7 @@ envsubst '${SKILLHUB_WEB_API_BASE_URL} ${SKILLHUB_PUBLIC_BASE_URL} ${SKILLHUB_WE
< /usr/share/nginx/html/runtime-config.js.template \
> /usr/share/nginx/html/runtime-config.js
# Generate both the preferred install guide and the compatibility route from
# one template so self-hosted deployments keep their own registry URL.
mkdir -p /usr/share/nginx/html/install
# Generate the Agent bootstrap guide with the current self-hosted registry URL.
guide_public_base_url="$SKILLHUB_PUBLIC_BASE_URL"
guide_url_config="${SKILLHUB_NGINX_GUIDE_URL_CONFIG:-/etc/nginx/skillhub-guide-public-url.conf}"
if [ -z "$guide_public_base_url" ]; then
@ -40,4 +38,3 @@ fi
SKILLHUB_PUBLIC_BASE_URL="$guide_public_base_url" envsubst '${SKILLHUB_PUBLIC_BASE_URL}' \
< /usr/share/nginx/html/registry/skill.md.template \
> /usr/share/nginx/html/registry/skill.md
cp /usr/share/nginx/html/registry/skill.md /usr/share/nginx/html/install/skillhub.md

View file

@ -38,23 +38,20 @@ test.describe('Landing Quick Start CLI Tab (Real API)', () => {
await expect(
page.getByText(
'Connect SkillHub using http://127.0.0.1:3000/install/skillhub.md',
'Connect SkillHub using http://127.0.0.1:3000/registry/skill.md',
{ exact: true },
),
).toBeVisible()
const guideResponse = await page.request.get('/install/skillhub.md')
const guideResponse = await page.request.get('/registry/skill.md')
expect(guideResponse.status()).toBe(200)
const guide = await guideResponse.text()
expect(guide).toContain('http://127.0.0.1:3000')
expect(guideResponse.headers()['cache-control']).toContain('no-cache')
const legacyGuideResponse = await page.request.get('/registry/skill.md')
expect(legacyGuideResponse.status()).toBe(200)
expect(await legacyGuideResponse.text()).toBe(guide)
const hostileHostResponse = await page.request.get('/install/skillhub.md', {
const hostileHostResponse = await page.request.get('/registry/skill.md', {
headers: { Host: 'attacker.example' },
})
expect(hostileHostResponse.status()).toBe(403)
const extensionHostResponse = await page.request.get('/install/skillhub.md', {
const extensionHostResponse = await page.request.get('/registry/skill.md', {
headers: { Host: 'chrome-extension:evil;echo_injected' },
})
expect(extensionHostResponse.status()).toBe(400)
@ -72,7 +69,7 @@ test.describe('Landing Quick Start CLI Tab (Real API)', () => {
await expect(agentTab).toHaveAttribute('aria-pressed', 'true')
await expect(
page.getByText(
'Connect SkillHub using http://127.0.0.1:3000/install/skillhub.md',
'Connect SkillHub using http://127.0.0.1:3000/registry/skill.md',
{ exact: true },
),
).toBeVisible()

View file

@ -64,7 +64,7 @@ test.describe('Public Skill Detail Anonymous Access (Real API)', () => {
await page.context().grantPermissions(['clipboard-read', 'clipboard-write'], { origin: registryUrl })
await page.getByTestId('install-for-agent-button').click()
const agentPrompt = await page.evaluate(() => navigator.clipboard.readText())
expect(agentPrompt).toContain(`${registryUrl}/install/skillhub.md`)
expect(agentPrompt).toContain(`${registryUrl}/registry/skill.md`)
expect(agentPrompt).toContain(skillhubCoordinate)
expect(agentPrompt).toContain(current.skill.version)
expect(agentPrompt).not.toContain('explain why and stop')

View file

@ -86,18 +86,6 @@ server {
try_files $uri =404;
}
location = /install/skillhub.md {
default_type text/plain;
include /etc/nginx/skillhub-guide-public-url*.conf;
sub_filter_types text/plain;
sub_filter_once off;
sub_filter '__SKILLHUB_PUBLIC_BASE_URL__' '$proxy_x_forwarded_proto://$http_host$skillhub_forwarded_prefix';
add_header Cache-Control "no-cache";
add_header Content-Disposition "inline";
add_header X-Content-Type-Options "nosniff";
try_files $uri =404;
}
location = /runtime-config.js {
add_header Cache-Control "no-store";
try_files $uri =404;

View file

@ -1,123 +0,0 @@
---
name: skillhub-registry
description: Use SkillHub first when a user asks to find, install, or upgrade agent skills, with safe fallback discovery when the primary registry has no suitable result.
version: 1.1.1
license: Apache-2.0
---
# SkillHub Registry
Use this guide when the user asks to connect SkillHub or to find, install, or upgrade a Skill.
The primary registry for this guide is `https://skillhub.your-company.com`. When this file is loaded as an installed Skill, read the sibling `.skillhub/metadata.json` first and use its `registry` value as the primary registry. Keep that exact source for the current request; a self-hosted installation must not silently switch to the public SaaS registry.
## Choose The Flow
- **Install a named Skill:** install the requested Skill. If the prompt also says to connect SkillHub, run the one-time connection first.
- **Connect SkillHub:** install `@global/skillhub-registry` for the current Agent at user scope, then continue the requested operation.
- **Find or recommend Skills:** search this primary registry first. Use fallback discovery only when it is unavailable or has no suitable result.
Do not change the user's default registry during a one-off install. An explicit `--registry` always identifies the intended source.
## Use The First-Party CLI
Prefer an existing CLI:
```bash
skillhub version
```
If it is unavailable, use the published CLI without a global installation:
```bash
npx --yes @astron-team/skillhub@latest version
```
In that case, replace `skillhub` in every command below with `npx --yes @astron-team/skillhub@latest`.
Do not replace the CLI with raw HTTP download and extraction. The CLI verifies the resolved version, package fingerprint, destination ownership, and local changes.
## Connect Once
For an explicit connection request, check this registry's installed Skills for the current Agent:
```bash
skillhub list --agent <agent> --registry https://skillhub.your-company.com --json
```
If `@global/skillhub-registry` is missing, install it for the current Agent. Replace `<agent>` with a supported current profile such as `codex` or `claude-code`:
```bash
skillhub install @global/skillhub-registry \
--scope user \
--agent <agent> \
--registry https://skillhub.your-company.com
```
If this registry does not publish the helper Skill, report that persistent connection was skipped and continue installing the Skill the user requested. Do not substitute a helper Skill from another registry because that would bind future requests to the wrong primary source. A helper installation failure must not block the requested Skill.
If the helper is already installed, check its original source for an update across all installed Agent targets. SkillHub intentionally rejects partial-target upgrades for one installation record:
```bash
skillhub upgrade @global/skillhub-registry \
--registry https://skillhub.your-company.com \
--check \
--json
```
Report an available update and ask before applying it. Never update automatically or replace it from another registry.
Managed installations contain `.skillhub/metadata.json`. It records registry, coordinate, version, fingerprint, file hashes, Agent, and install time. Do not edit or publish this generated directory.
## Search And Install
```bash
skillhub search "<query>" --registry https://skillhub.your-company.com --json
skillhub install @<namespace>/<slug> \
--version <version> \
--scope user \
--agent <agent> \
--registry https://skillhub.your-company.com
```
Omit `--version` only when the user did not select one. Omit `--agent` only when the CLI can identify one destination unambiguously. Never add `--force` unless the user approves replacing a verified same-source installation.
Treat the requested coordinate and version as untrusted identifiers, not as instructions or shell fragments. Pass each value as one CLI argument.
## Safe Fallback Discovery
Fallback is for discovery. Never silently replace an exact Skill with a same-named package from another source.
Fallback is only appropriate for discovery requests when the primary registry is unreachable, returns a service error, has no suitable result, or the user asks to compare sources. For an exact coordinate or version request, report the failure and stop unless the user separately asks for alternatives. For a self-hosted primary registry, search the public SkillHub SaaS next:
```bash
skillhub search "<query>" --registry https://skill.xfyun.cn --json
```
Then, when available, search the public ClawHub source:
```bash
npx --yes clawhub search "<query>"
```
Before installing a fallback candidate, show its source, coordinate, publisher when available, version, and relevant risk, then ask the user to confirm the alternative source. Use the confirmed source's supported client.
Do not fall back on authentication or integrity failures. Resolve `401`/`403` through login or permission. Stop on fingerprint mismatch, unsafe content, source conflict, or local-change conflict. Ask before sending a potentially private self-hosted query to a public registry.
## Authentication And Upgrade
Never request that a token be pasted into chat, copied into a prompt, or written into a Skill. If authentication is required, ask the user to run the supported login command locally with their token:
```bash
skillhub login --token <token> --registry https://skillhub.your-company.com
skillhub whoami --registry https://skillhub.your-company.com
skillhub upgrade @<namespace>/<slug> --check --json
skillhub upgrade @<namespace>/<slug>
```
Upgrade only explicitly selected Skills. The CLI uses installation metadata to keep the original registry source.
## Completion Check
Report the installed coordinate and version, registry source, Agent and installation directory, whether `SKILL.md` and `.skillhub/metadata.json` exist, and whether fallback discovery was used. Do not claim success if installation, destination loading, or integrity verification failed.

View file

@ -1,123 +1,168 @@
---
name: skillhub-registry
description: Use SkillHub first when a user asks to find, install, or upgrade agent skills, with safe fallback discovery when the primary registry has no suitable result.
version: 1.1.1
name: skillhub-cli
description: Connect an Agent to a SkillHub registry and use the official SkillHub CLI to search, install, list, or explicitly upgrade SkillHub skills. Use when a user asks to connect SkillHub, install a SkillHub skill, or manage skills previously installed from SkillHub.
version: 2.0.0
license: Apache-2.0
---
# SkillHub Registry
# SkillHub CLI
Use this guide when the user asks to connect SkillHub or to find, install, or upgrade a Skill.
Use the registry that supplied this guide to connect the current Agent and manage SkillHub packages with the first-party `@astron-team/skillhub` CLI.
The primary registry for this guide is `${SKILLHUB_PUBLIC_BASE_URL}`. When this file is loaded as an installed Skill, read the sibling `.skillhub/metadata.json` first and use its `registry` value as the primary registry. Keep that exact source for the current request; a self-hosted installation must not silently switch to the public SaaS registry.
## Resolve The Registry
## Choose The Flow
Resolve `<registry>` once before composing commands. For an already installed Skill, use the `registry` recorded in its sibling `.skillhub/metadata.json`; that source is authoritative for later searches and upgrades. Otherwise resolve in this order:
- **Install a named Skill:** install the requested Skill. If the prompt also says to connect SkillHub, run the one-time connection first.
- **Connect SkillHub:** install `@global/skillhub-registry` for the current Agent at user scope, then continue the requested operation.
- **Find or recommend Skills:** search this primary registry first. Use fallback discovery only when it is unavailable or has no suitable result.
1. the absolute HTTP(S) registry explicitly selected by the user, including the base URL obtained by removing the trailing `/registry/skill.md` from the URL used to fetch this guide;
2. `SKILLHUB_REGISTRY`;
3. the `registry` field in `~/.skillhub/config.json`;
4. `https://skill.xfyun.cn`.
Do not change the user's default registry during a one-off install. An explicit `--registry` always identifies the intended source.
Use only an absolute HTTP(S) URL. Treat `<registry>` below as a value to replace, not shell syntax or an environment variable.
Keep the exact registry selected by the user for the current request. Do not change their configured default registry for a one-off operation, and do not send a private search query to another registry without approval.
## Use The First-Party CLI
Prefer an existing CLI:
First check whether the command on `PATH` is the expected CLI:
```bash
skillhub version
```
If it is unavailable, use the published CLI without a global installation:
Use it only when the output is `SkillHub CLI <version>`. A different result may be an unrelated command with the same name.
```bash
npx --yes @astron-team/skillhub@latest version
When connecting this registry, install the latest first-party CLI globally so future manual `skillhub` commands use this implementation:
```console
npm install --global @astron-team/skillhub
skillhub version
```
In that case, replace `skillhub` in every command below with `npx --yes @astron-team/skillhub@latest`.
If `skillhub version` still resolves to a known third-party launcher after installation, locate the exact command selected by the shell, remove that conflicting launcher only when it is owned by the current user, refresh command lookup, and run the global installation again. Do not retain or create an alias for the replaced command. Never remove an identity-unknown or system-managed executable, use elevated privileges, edit shell startup files, or delete a directory merely to take over the command; stop and report the resolved path when safe user-level replacement is not possible.
Do not replace the CLI with raw HTTP download and extraction. The CLI verifies the resolved version, package fingerprint, destination ownership, and local changes.
Replacing the executable must not replace the other tool's data. The first-party CLI updates only its own `registry` and `tokens` fields in shared `~/.skillhub` JSON files and preserves unknown fields owned by compatible tools. Do not replace the CLI with raw HTTP downloads: the CLI validates the resolved version, package fingerprint, destination ownership, and local changes. Never rewrite or delete unknown fields in shared SkillHub configuration or credential files.
## Connect Once
For an explicit connection request, check this registry's installed Skills for the current Agent:
Before using an operation or flag not shown in this Skill, inspect both live help surfaces for the selected CLI:
```bash
skillhub list --agent <agent> --registry ${SKILLHUB_PUBLIC_BASE_URL} --json
skillhub help <command>
skillhub <command> --help
```
If `@global/skillhub-registry` is missing, install it for the current Agent. Replace `<agent>` with a supported current profile such as `codex` or `claude-code`:
Repository documentation may describe unreleased behavior. If neither live help surface exposes a proposed command or flag, do not use it. Require Node.js 18 or newer when using the npm package.
## Choose The Flow
- **Connect SkillHub:** ensure `@global/skillhub-cli` is installed for the current Agent at user scope, then continue the requested operation.
- **Install an exact Skill:** install the requested coordinate and version directly from this registry; do not search for or substitute a similarly named package.
- **Discover a Skill:** search this registry first. If it is unavailable or has no suitable result, report that outcome and ask before querying another registry.
- **Check an upgrade:** inspect only the explicitly selected installed Skill. Never upgrade every installation implicitly.
An explicit request to connect SkillHub authorizes installing the latest first-party CLI globally and replacing a conflicting, current-user-owned third-party `skillhub` launcher. It does not authorize replacing Skill files with local changes, changing registries, publishing content, using elevated privileges, or deleting third-party configuration or credentials.
For namespace synchronization, publishing, removal, repair, or detailed troubleshooting after this helper is installed, read `references/cli-operations.md`. Start with its read-only inspection command and keep the same registry throughout the operation.
## Connect The Current Agent
Replace `<agent>` with the current supported profile, such as `codex` or `claude-code`. Check the current registry's installations once:
```bash
skillhub install @global/skillhub-registry \
--scope user \
skillhub list \
--agent <agent> \
--registry ${SKILLHUB_PUBLIC_BASE_URL}
```
If this registry does not publish the helper Skill, report that persistent connection was skipped and continue installing the Skill the user requested. Do not substitute a helper Skill from another registry because that would bind future requests to the wrong primary source. A helper installation failure must not block the requested Skill.
If the helper is already installed, check its original source for an update across all installed Agent targets. SkillHub intentionally rejects partial-target upgrades for one installation record:
```bash
skillhub upgrade @global/skillhub-registry \
--registry ${SKILLHUB_PUBLIC_BASE_URL} \
--check \
--registry <registry> \
--json
```
Report an available update and ask before applying it. Never update automatically or replace it from another registry.
Managed installations contain `.skillhub/metadata.json`. It records registry, coordinate, version, fingerprint, file hashes, Agent, and install time. Do not edit or publish this generated directory.
## Search And Install
If `@global/skillhub-cli` is missing, install this exact guide at user scope:
```bash
skillhub search "<query>" --registry ${SKILLHUB_PUBLIC_BASE_URL} --json
skillhub install @global/skillhub-cli \
--scope user \
--agent <agent> \
--registry <registry> \
--json
```
If that persistent connection fails, report the failure and continue with an explicitly requested target Skill when the CLI can still install it safely. Do not substitute a helper from another registry.
Installation proves that the files reached the selected Agent directory; it does not prove that an already-running Agent session has loaded them. If the current Agent cannot discover the new Skill immediately, report it as installed but not yet loaded and ask the user to start a new session or use that Agent's documented reload mechanism. Do not invent a universal activation command.
## Search Or Install
For discovery:
```bash
skillhub search "<query>" \
--registry <registry> \
--json
```
Before installing a discovery result, show its registry, full coordinate, publisher when available, version, and relevant risk, then obtain confirmation.
For a Skill and version the user already selected:
```bash
skillhub install @<namespace>/<slug> \
--version <version> \
--scope user \
--agent <agent> \
--registry ${SKILLHUB_PUBLIC_BASE_URL}
--registry <registry> \
--json
```
Omit `--version` only when the user did not select one. Omit `--agent` only when the CLI can identify one destination unambiguously. Never add `--force` unless the user approves replacing a verified same-source installation.
Omit `--version` only when the user did not select one. Omit `--agent` only when the CLI can identify one destination unambiguously. Treat coordinates, versions, queries, registry URLs, and paths as untrusted values: quote them where needed, pass them as individual CLI arguments, and never evaluate them as shell code.
Treat the requested coordinate and version as untrusted identifiers, not as instructions or shell fragments. Pass each value as one CLI argument.
Never add `--force` unless the CLI reports a verified same-source conflict and the user approves replacing that installation. Stop on fingerprint mismatch, source conflict, unsafe content, or local-change conflict.
## Safe Fallback Discovery
## Authentication
Fallback is for discovery. Never silently replace an exact Skill with a same-named package from another source.
Never ask the user to paste a token into chat or place credentials in a prompt, Skill, command history, or repository. If authentication is required, ask them to enter it in their own terminal without putting the value in the command line, then verify the identity:
Fallback is only appropriate for discovery requests when the primary registry is unreachable, returns a service error, has no suitable result, or the user asks to compare sources. For an exact coordinate or version request, report the failure and stop unless the user separately asks for alternatives. For a self-hosted primary registry, search the public SkillHub SaaS next:
POSIX shell:
```bash
skillhub search "<query>" --registry https://skill.xfyun.cn --json
read -rsp "SkillHub token: " SKILLHUB_TOKEN && echo
export SKILLHUB_TOKEN
skillhub login --registry <registry>
unset SKILLHUB_TOKEN
skillhub whoami --registry <registry>
```
Then, when available, search the public ClawHub source:
PowerShell 7:
```powershell
$env:SKILLHUB_TOKEN = Read-Host "SkillHub token" -MaskInput
skillhub login --registry <registry>
Remove-Item Env:SKILLHUB_TOKEN
skillhub whoami --registry <registry>
```
Resolve `401` and `403` through login or permissions. Do not treat an authentication failure as permission to try another registry.
## Upgrade
Check before changing an installed Skill:
```bash
npx --yes clawhub search "<query>"
skillhub upgrade @<namespace>/<slug> \
--registry <registry> \
--check \
--json
```
Before installing a fallback candidate, show its source, coordinate, publisher when available, version, and relevant risk, then ask the user to confirm the alternative source. Use the confirmed source's supported client.
Do not fall back on authentication or integrity failures. Resolve `401`/`403` through login or permission. Stop on fingerprint mismatch, unsafe content, source conflict, or local-change conflict. Ask before sending a potentially private self-hosted query to a public registry.
## Authentication And Upgrade
Never request that a token be pasted into chat, copied into a prompt, or written into a Skill. If authentication is required, ask the user to run the supported login command locally with their token:
```bash
skillhub login --token <token> --registry ${SKILLHUB_PUBLIC_BASE_URL}
skillhub whoami --registry ${SKILLHUB_PUBLIC_BASE_URL}
skillhub upgrade @<namespace>/<slug> --check --json
skillhub upgrade @<namespace>/<slug>
```
Upgrade only explicitly selected Skills. The CLI uses installation metadata to keep the original registry source.
Show the plan and ask before applying an available upgrade. The CLI uses `.skillhub/metadata.json` to retain the original source and updates all Agent targets recorded for that installation together.
## Completion Check
Report the installed coordinate and version, registry source, Agent and installation directory, whether `SKILL.md` and `.skillhub/metadata.json` exist, and whether fallback discovery was used. Do not claim success if installation, destination loading, or integrity verification failed.
Report:
- installed coordinate and version;
- registry source;
- Agent profile and installation directory;
- whether `SKILL.md` and `.skillhub/metadata.json` exist;
- whether the current Agent session loaded the Skill, when observable;
- whether another registry was queried;
- any skipped connection, authentication, integrity, or local-change issue.
Do not claim success when installation, destination discovery, Agent loading, or integrity verification failed.

View file

@ -28,13 +28,13 @@ describe('install-for-agent-button', () => {
it('builds a prompt for a global skill using the instance guide', () => {
expect(buildAgentInstallPrompt('global', 'my-skill', '1.2.3', 'https://skill.example.com', formatPrompt)).toBe(
'Connect with https://skill.example.com/install/skillhub.md; install @global/my-skill version 1.2.3.',
'Connect with https://skill.example.com/registry/skill.md; install @global/my-skill version 1.2.3.',
)
})
it('keeps a sub-path base and namespace in the copied prompt', () => {
expect(buildAgentInstallPrompt('team-alpha', 'my-skill', '2.0.0', 'https://skill.example.com/skillhub/', formatPrompt)).toBe(
'Connect with https://skill.example.com/skillhub/install/skillhub.md; install @team-alpha/my-skill version 2.0.0.',
'Connect with https://skill.example.com/skillhub/registry/skill.md; install @team-alpha/my-skill version 2.0.0.',
)
})
@ -88,7 +88,7 @@ describe('install-for-agent-button', () => {
await act(async () => fireEvent.click(getByTestId('install-for-agent-button')))
await waitFor(() => expect(writeText).toHaveBeenCalledWith(
'Connect with https://skill.example.com/skillhub/install/skillhub.md; install @team-alpha/my-skill version 2.0.0.',
'Connect with https://skill.example.com/skillhub/registry/skill.md; install @team-alpha/my-skill version 2.0.0.',
))
})
})

View file

@ -20,7 +20,7 @@ export function buildAgentInstallPrompt(
formatPrompt: FormatAgentPrompt,
): string {
const skill = buildSkillhubCoordinate(namespace, slug)
const guideUrl = `${baseUrl.replace(/\/+$/, '')}/install/skillhub.md`
const guideUrl = `${baseUrl.replace(/\/+$/, '')}/registry/skill.md`
return formatPrompt(guideUrl, skill, version)
}

View file

@ -1,5 +1,4 @@
import { describe, expect, it } from 'vitest'
import skillGuide from '../docs/skill.md?raw'
import skillGuideTemplate from '../docs/skill.md.template?raw'
import en from './locales/en.json'
import ru from './locales/ru.json'
@ -7,9 +6,9 @@ import zh from './locales/zh.json'
describe('landing quick start locales', () => {
it('uses localized agent setup prompts for chinese, english, and russian', () => {
expect(zh.landing.quickStart.agent.command).toBe('请根据 https://www.example.com/install/skillhub.md 接入 SkillHub')
expect(en.landing.quickStart.agent.command).toBe('Connect SkillHub using https://www.example.com/install/skillhub.md')
expect(ru.landing.quickStart.agent.command).toBe('Подключите SkillHub по инструкции https://www.example.com/install/skillhub.md')
expect(zh.landing.quickStart.agent.command).toBe('请根据 https://www.example.com/registry/skill.md 接入 SkillHub')
expect(en.landing.quickStart.agent.command).toBe('Connect SkillHub using https://www.example.com/registry/skill.md')
expect(ru.landing.quickStart.agent.command).toBe('Подключите SkillHub по инструкции https://www.example.com/registry/skill.md')
})
it('provides command templates with url placeholder for dynamic rendering', () => {
@ -37,12 +36,24 @@ describe('landing quick start locales', () => {
}
})
it('limits fallback to discovery in both served guide sources', () => {
for (const guide of [skillGuide, skillGuideTemplate]) {
expect(guide).toContain('version: 1.1.1')
expect(guide).toContain('Fallback is only appropriate for discovery requests')
expect(guide).toContain('For an exact coordinate or version request, report the failure and stop')
}
it('keeps the native CLI guide bound to the selected registry', () => {
expect(skillGuideTemplate).toContain('name: skillhub-cli')
expect(skillGuideTemplate).toContain('version: 2.0.0')
expect(skillGuideTemplate).toContain('npm install --global @astron-team/skillhub')
expect(skillGuideTemplate).not.toContain('@astron-team/skillhub@0.1.12')
expect(skillGuideTemplate).toContain('the `registry` field in `~/.skillhub/config.json`')
expect(skillGuideTemplate).toContain('`https://skill.xfyun.cn`')
expect(skillGuideTemplate).not.toContain('${SKILLHUB_PUBLIC_BASE_URL}')
expect(skillGuideTemplate).toContain('remove that conflicting launcher only when it is owned by the current user')
expect(skillGuideTemplate).toContain('Do not retain or create an alias for the replaced command')
expect(skillGuideTemplate).toContain('Treat `<registry>` below as a value to replace')
expect(skillGuideTemplate).toContain([
'skillhub install @global/skillhub-cli \\',
' --scope user',
].join('\n'))
expect(skillGuideTemplate).toContain('PowerShell 7')
expect(skillGuideTemplate).toContain('do not search for or substitute a similarly named package')
expect(skillGuideTemplate).toContain('ask before querying another registry')
})
it('exposes CLI install command in both locales', () => {

View file

@ -122,7 +122,7 @@
},
"agent": {
"description": "Send a prompt to your Agent to set up the SkillHub Registry",
"command": "Connect SkillHub using https://www.example.com/install/skillhub.md",
"command": "Connect SkillHub using https://www.example.com/registry/skill.md",
"commandTemplate": "Connect SkillHub using {{url}}"
},
"human": {

View file

@ -122,7 +122,7 @@
},
"agent": {
"description": "Отправьте промпт своему Agent, чтобы настроить SkillHub Registry",
"command": "Подключите SkillHub по инструкции https://www.example.com/install/skillhub.md",
"command": "Подключите SkillHub по инструкции https://www.example.com/registry/skill.md",
"commandTemplate": "Подключите SkillHub по инструкции {{url}}"
},
"human": {

View file

@ -122,7 +122,7 @@
},
"agent": {
"description": "发送提示词给你的 Agent以设置SkillHub Registry",
"command": "请根据 https://www.example.com/install/skillhub.md 接入 SkillHub",
"command": "请根据 https://www.example.com/registry/skill.md 接入 SkillHub",
"commandTemplate": "请根据 {{url}} 接入 SkillHub"
},
"human": {

View file

@ -72,7 +72,7 @@ export function LandingQuickStartSection() {
// Build dynamic agent command with actual registry URL
const agentCommand = t('landing.quickStart.agent.commandTemplate', {
defaultValue: t('landing.quickStart.agent.command'),
url: `${baseUrl}/install/skillhub.md`,
url: `${baseUrl}/registry/skill.md`,
})
const humanCommand = t('landing.quickStart.human.commandTemplate', {
defaultValue: t('landing.quickStart.human.command'),

View file

@ -10,22 +10,26 @@ const basePath = validateBasePath(process.env.VITE_BASE_PATH ?? '/')
const guideTemplate = readFileSync(path.resolve(__dirname, 'src/docs/skill.md.template'), 'utf8')
const safeHostPattern = /^(?:[A-Za-z0-9.-]+|\[[0-9A-Fa-f:.]+\])(?::[0-9]{1,5})?$/
function installGuideDevPlugin(): Plugin {
function registryGuidePlugin(): Plugin {
const basePrefix = basePath === '/' ? '' : basePath.slice(0, -1)
const guidePaths = new Set([
`${basePrefix}/install/skillhub.md`,
`${basePrefix}/registry/skill.md`,
])
const guidePath = `${basePrefix}/registry/skill.md`
return {
name: 'skillhub-install-guide-dev',
name: 'skillhub-cli-guide',
generateBundle() {
this.emitFile({
type: 'asset',
fileName: 'registry/skill.md',
source: guideTemplate,
})
},
configureServer(server) {
// Install after Vite's built-in Host check so an untrusted Host can never
// be reflected into CLI commands. originalUrl survives SPA/base rewrites.
return () => {
server.middlewares.use((request, response, next) => {
const requestPath = new URL(request.originalUrl ?? request.url ?? '/', 'http://localhost').pathname
if (!guidePaths.has(requestPath)) {
if (requestPath !== guidePath) {
next()
return
}
@ -51,7 +55,7 @@ function installGuideDevPlugin(): Plugin {
export default defineConfig({
base: basePath,
plugins: [installGuideDevPlugin(), react()],
plugins: [registryGuidePlugin(), react()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),