
Byted Skillhub Download
- 57 installs
- 411 repo stars
- Updated August 4, 2026
- bytedance/agentkit-samples
byted-skillhub-download is a Claude skill that downloads and extracts private or enterprise skills from SkillHub by exact name.
About
A skill that downloads private or enterprise-only skills from SkillHub by exact name and auto-extracts the latest version zip. A developer uses it to pull internal or authorized skill packages stored in a company SkillHub space. It reads the SkillHub API credentials and space ID from injected environment variables and prints the extracted directory path.
- Downloads private or enterprise-only skills from SkillHub by exact name
- Auto-extracts the downloaded zip into a directory named after the skill
- Reads SkillHub API base, key, and space ID from injected environment variables
Byted Skillhub Download by the numbers
- 57 all-time installs (skills.sh)
- Ranked #311 of 782 Skill Development skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
byted-skillhub-download capabilities & compatibility
- Capabilities
- skill download
What byted-skillhub-download says it does
从 SkillHub 下载私有或企业专属技能。当涉及下载内部、私有、企业专属或需授权的技能时使用。
python ./scripts/download_from_skillhub.py --name "<精确名称>" --output /root/.openclaw/workspace/skills/
npx skills add https://github.com/bytedance/agentkit-samples --skill byted-skillhub-downloadAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 57 |
|---|---|
| repo stars | ★ 411 |
| Last updated | August 4, 2026 |
| Repository | bytedance/agentkit-samples ↗ |
What it does
Download a named private or enterprise skill from SkillHub and auto-extract it into the skills directory.
Who is it for?
pulling private or enterprise-exclusive skills from a company SkillHub space
Skip if: downloading ordinary public skills, which have other download paths
When should I use this skill?
user asks to download an internal, private, or enterprise-exclusive skill
What you get
A named private skill is downloaded from SkillHub and extracted, with the resulting directory path printed.
- extracted private skill directory (or zip with --no-extract)
By the numbers
- 3 injected environment variables
Files
byted-skillhub-download
用途
- 从 SkillHub 精确按名称定位并下载私有或企业专属技能的最新版本 zip 包。
- 默认自动解压:下载后会自动解压至以技能名称命名的目录中,并平铺顶层目录结构。
- 下载并解压完成后输出目录路径。
何时调用
- 当用户需求涉及下载私有技能或企业专属技能时触发。常见语义包括:
1. 明确要求从 byted-skillhub-download 下载。 2. 提到“下载企业专属技能”、“下载内部技能”或“下载私有 skill/技能”。 3. 需要拉取存储在公司/企业 SkillHub 空间内的特定私有资源包。
- 注意:对于普通的公开技能(Public Skills),请优先使用其他公开下载能力。
环境说明 (Agent 必读)
- 无需向用户索要环境变量:当前环境已自动注入以下变量:
ARK_SKILL_API_BASE: SkillHub API 服务地址ARK_SKILL_API_KEY: 企业 SkillHub 访问密钥SKILLHUB_SKILL_SPACE_ID: 企业专属技能空间 ID- 严禁向用户询问参数值:Agent 在触发此技能时,应直接从环境中读取这些变量并执行脚本,不得中断流程向用户确认这些信息。
下载步骤 1. 执行脚本(路径相对于本 SKILL.md 所在目录)
python ./scripts/download_from_skillhub.py --name "<精确名称>" --output /root/.openclaw/workspace/skills/- 默认会将技能解压到
/root/.openclaw/workspace/skills/<skill_name>/目录下 - 若只想下载 zip 包而不解压,请添加
--no-extract参数
2. 观察输出
- 成功时,最后一行将打印解压后的目录路径(或 zip 文件路径)
安装提示
- 本技能仅下载并解压,不负责后续的安装逻辑。
示例
- 下载名为 "awesome-tool" 的技能至默认目录
python ./scripts/download_from_skillhub.py --name "awesome-tool" --output /root/.openclaw/workspace/skills/
故障排查
- 如提示缺少环境变量,请确认在环境中运行或联系管理员
- 未找到技能:确认名称为精确匹配,或检查 SkillSpaceId 是否正确
- 网络或鉴权错误:检查网络连通性与 API Key 是否有效
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 [yyyy] [name of copyright owner]
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.import argparse
import json
import os
import re
import urllib.error
import urllib.request
import zipfile
import shutil
from pathlib import Path
from typing import Any, Dict, Optional, Tuple
def _normalize_api_base(api_base: str) -> str:
formatted = api_base.strip()
if not formatted.startswith("http://") and not formatted.startswith("https://"):
formatted = f"http://{formatted}"
return formatted.rstrip("/")
def _read_env() -> Tuple[str, str, Optional[str]]:
api_host = os.getenv("ARK_SKILL_API_BASE")
api_key = os.getenv("ARK_SKILL_API_KEY")
skill_space_id = os.getenv("SKILLHUB_SKILL_SPACE_ID")
if not api_host or not api_key:
raise SystemExit("缺少必要环境变量: ARK_SKILL_API_BASE, ARK_SKILL_API_KEY")
sid = skill_space_id.strip() if skill_space_id else None
if not sid:
sid = None
return _normalize_api_base(api_host), api_key.strip(), sid
def _post_json(
url: str,
payload: Dict[str, Any],
api_key: str,
extra_headers: Optional[Dict[str, str]] = None,
) -> Tuple[bytes, Dict[str, str]]:
data = json.dumps(payload).encode("utf-8")
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {api_key}",
"ServiceName": "skillhub",
}
if extra_headers:
headers.update(extra_headers)
req = urllib.request.Request(url, data=data, headers=headers, method="POST")
with urllib.request.urlopen(req, timeout=30) as resp:
body = resp.read()
resp_headers = {k: v for k, v in resp.getheaders()}
return body, resp_headers
def _json_loads_or_none(data: bytes) -> Optional[Dict[str, Any]]:
try:
return json.loads(data.decode("utf-8"))
except Exception:
return None
def _sanitize_name(name: str) -> str:
return re.sub(r"[^A-Za-z0-9_.-]+", "-", name).strip("-")
def list_skills(
api_host: str, api_key: str, skill_space_id: Optional[str], name: str
) -> Dict[str, Any]:
url = f"{api_host}/ListSkills"
filter_obj: Dict[str, Any] = {"Name": name}
if skill_space_id:
filter_obj["SkillSpaceId"] = skill_space_id
payload: Dict[str, Any] = {
"PageNumber": 1,
"PageSize": 50,
"Filter": filter_obj,
}
print(f"[DEBUG] 调用 ListSkills: url={url}")
print(f"[DEBUG] 请求 payload: {json.dumps(payload, ensure_ascii=False)}")
body, resp_headers = _post_json(url, payload, api_key)
print(f"[DEBUG] 响应头: {resp_headers}")
print(f"[DEBUG] 响应体长度: {len(body)} 字节")
try:
body_str = body.decode("utf-8")
print(f"[DEBUG] 响应体前 1000 字符: {body_str[:1000]}...")
except Exception as e:
print(f"[DEBUG] 响应体解码失败: {e}")
data = _json_loads_or_none(body)
if not data:
raise SystemExit("ListSkills 响应不是有效的 JSON")
# 处理可能的响应包装结构
if "Items" not in data:
print(f"[DEBUG] 响应 JSON: {json.dumps(data, ensure_ascii=False)}")
# 尝试从 Result 或 Response 字段获取
wrapped = None
if "Result" in data and isinstance(data["Result"], dict):
print("[DEBUG] 尝试从 Result 字段获取 Items")
wrapped = data["Result"]
elif "Response" in data and isinstance(data["Response"], dict):
print("[DEBUG] 尝试从 Response 字段获取 Items")
wrapped = data["Response"]
if wrapped and "Items" in wrapped:
data = wrapped
else:
raise SystemExit("ListSkills 响应不包含 Items 字段")
print(f"[DEBUG] Items 数量: {len(data.get('Items', []))}")
return data
def pick_exact_skill(data: Dict[str, Any], name: str) -> Dict[str, Any]:
items = data.get("Items") or []
exact = [it for it in items if isinstance(it, dict) and it.get("Name") == name]
if not exact:
raise SystemExit(f"未找到名称精确匹配的 skill: {name}")
return exact[0]
def extract_version_info(skill: Dict[str, Any]) -> Tuple[Optional[str], Optional[str]]:
version_id = None
version_str = None
lvs = skill.get("LatestVersionStatus") or {}
if isinstance(lvs, dict):
for k in ("VersionId", "VersionID", "Id", "ID"):
if k in lvs and isinstance(lvs[k], str) and lvs[k]:
version_id = lvs[k]
break
for k in ("Version",):
if k in lvs and isinstance(lvs[k], str) and lvs[k]:
version_str = lvs[k]
break
if not version_str:
versions = skill.get("Versions")
if isinstance(versions, list) and versions:
version_str = str(versions[-1])
if not version_str:
vins = skill.get("VersionsInfo")
if isinstance(vins, list) and vins:
last = vins[-1]
if isinstance(last, dict) and isinstance(last.get("Version"), str):
version_str = last.get("Version")
for k in ("VersionId", "VersionID", "Id", "ID"):
if (
isinstance(last, dict)
and isinstance(last.get(k), str)
and last.get(k)
):
version_id = last.get(k)
break
return version_id, version_str
def download_skill(
api_host: str, api_key: str, skill_id: str, version_id: Optional[str]
) -> Tuple[bytes, Dict[str, str]]:
url = f"{api_host}/DownloadSkill"
payload: Dict[str, Any] = {"SkillId": skill_id, "IsPreview": False}
if version_id:
payload["SkillVersionId"] = version_id
print(f"[DEBUG] 调用 DownloadSkill: url={url}")
print(f"[DEBUG] 请求 payload: {json.dumps(payload, ensure_ascii=False)}")
body, headers = _post_json(
url,
payload,
api_key,
extra_headers={"X-Skill-Caller": "arkclaw-business"},
)
print(f"[DEBUG] 响应头: {headers}")
print(f"[DEBUG] 响应体长度: {len(body)} 字节")
ctype = (headers.get("Content-Type") or "").lower()
print(f"[DEBUG] Content-Type: {ctype}")
# 优先尝试按二进制流处理
if "application/json" in ctype:
print("[DEBUG] 响应为 JSON,尝试解析")
data = _json_loads_or_none(body)
if isinstance(data, dict) and data.get("ZipBase64"):
import base64
print(f"[DEBUG] 检测到 ZipBase64 字段,长度: {len(data['ZipBase64'])} 字符")
return base64.b64decode(data["ZipBase64"]), headers
# 若 JSON 不包含 ZipBase64,仍按二进制流处理(可能是错误信息但保持兼容)
print("[DEBUG] JSON 不包含 ZipBase64,按二进制流处理")
else:
print("[DEBUG] 响应为二进制流,直接返回")
return body, headers
def main():
parser = argparse.ArgumentParser(prog="download_from_skillhub")
parser.add_argument("--name", required=True, help="Skill 名称,精确匹配")
parser.add_argument(
"--output", default="/root/.openclaw/workspace/skills/", help="输出路径。可为目录或文件名"
)
parser.add_argument(
"--no-extract", action="store_true", help="只下载 zip 包,不自动解压"
)
args = parser.parse_args()
api_host, api_key, space_id = _read_env()
data = list_skills(api_host, api_key, space_id, args.name)
skill = pick_exact_skill(data, args.name)
skill_id = skill.get("Id")
skill_name = skill.get("Name", args.name) # 优先使用 API 返回的名称
if not isinstance(skill_id, str) or not skill_id:
raise SystemExit("ListSkills 返回的 Skill 未包含有效的 Id")
version_id, version_str = extract_version_info(skill)
zip_bytes, _ = download_skill(api_host, api_key, skill_id, version_id)
out_path = Path(args.output)
base = _sanitize_name(skill_name)
if args.no_extract:
# 只下载 zip 包逻辑
if out_path.exists() and out_path.is_dir():
file_path = out_path / f"{base}.zip"
else:
if str(out_path).endswith(os.sep) or str(out_path).endswith("/"):
out_path = Path(str(out_path).rstrip("/"))
if out_path.suffix.lower() != ".zip":
out_path = out_path.with_suffix(".zip")
file_path = out_path
file_path.parent.mkdir(parents=True, exist_ok=True)
with open(file_path, "wb") as f:
f.write(zip_bytes)
print(str(file_path))
else:
# 下载并解压逻辑
target_dir = (
out_path
if not out_path.is_dir() and not out_path.exists()
else out_path / base
)
target_dir.mkdir(parents=True, exist_ok=True)
# 使用临时文件保存 zip
temp_zip = target_dir / f"{base}.zip.tmp"
with open(temp_zip, "wb") as f:
f.write(zip_bytes)
# 解压
try:
with zipfile.ZipFile(temp_zip, "r") as zf:
zf.extractall(target_dir)
# 尝试平铺:如果解压后只有一个顶层目录,将其内容移动到上一级
items = [it for it in target_dir.iterdir() if it.name != temp_zip.name]
if len(items) == 1 and items[0].is_dir():
inner_dir = items[0]
for child in inner_dir.iterdir():
shutil.move(str(child), str(target_dir))
inner_dir.rmdir()
print(f"[DEBUG] 已解压至目录: {target_dir}")
finally:
if temp_zip.exists():
temp_zip.unlink()
print(str(target_dir))
if __name__ == "__main__":
try:
main()
except urllib.error.HTTPError as e:
try:
detail = e.read().decode("utf-8")
except Exception:
detail = ""
raise SystemExit(f"HTTP 错误: {e.code} {e.reason} {detail}".strip())
except urllib.error.URLError as e:
raise SystemExit(f"网络错误: {e.reason}")
except KeyboardInterrupt:
raise SystemExit("已中断")
Related skills
FAQ
Do I need to supply credentials?
No. The API base, key, and space ID are injected as environment variables automatically.
Can I skip extraction?
Yes, pass --no-extract to download only the zip.