313 lines
8.7 KiB
Bash
313 lines
8.7 KiB
Bash
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
MODE="all"
|
|
RESTART_GATEWAY=0
|
|
SKILLS_PREF="default"
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--cli-only)
|
|
MODE="cli"
|
|
shift
|
|
;;
|
|
--skill-only)
|
|
MODE="skill"
|
|
shift
|
|
;;
|
|
--plugin-only)
|
|
MODE="plugin"
|
|
shift
|
|
;;
|
|
--restart-gateway)
|
|
RESTART_GATEWAY=1
|
|
shift
|
|
;;
|
|
--no-skills)
|
|
SKILLS_PREF="off"
|
|
shift
|
|
;;
|
|
--with-skills)
|
|
SKILLS_PREF="on"
|
|
shift
|
|
;;
|
|
-h|--help)
|
|
cat <<'USAGE'
|
|
Usage: install.sh [--cli-only|--skill-only|--plugin-only] [--no-skills|--with-skills] [--restart-gateway]
|
|
|
|
Installs the skillhub CLI.
|
|
Default mode installs CLI + workspace skill (find-skill style).
|
|
Use --plugin-only only when you explicitly want legacy plugin injection.
|
|
Use --no-skills to skip installing workspace skills and persist this preference
|
|
for OTA self-upgrade migrations.
|
|
USAGE
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "Error: unknown argument: $1" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
# Supports two archive layouts:
|
|
# 1) install.sh at kit root: ./install.sh + ./cli + ./plugin + ./skill
|
|
# 2) install.sh inside cli folder: ./cli/install.sh + ./cli/plugin + ./cli/skill + cli files
|
|
if [[ -d "${SCRIPT_DIR}/cli" ]]; then
|
|
CLI_SRC_DIR="${SCRIPT_DIR}/cli"
|
|
PLUGIN_SRC_DIR="${SCRIPT_DIR}/plugin"
|
|
SKILL_SRC_DIR="${SCRIPT_DIR}/skill"
|
|
else
|
|
CLI_SRC_DIR="${SCRIPT_DIR}"
|
|
PLUGIN_SRC_DIR="${SCRIPT_DIR}/plugin"
|
|
SKILL_SRC_DIR="${SCRIPT_DIR}/skill"
|
|
fi
|
|
|
|
INSTALL_BASE="${HOME}/.skillhub"
|
|
BIN_DIR="${HOME}/.local/bin"
|
|
CLI_TARGET="${INSTALL_BASE}/skills_store_cli.py"
|
|
UPGRADE_MODULE_TARGET="${INSTALL_BASE}/skills_upgrade.py"
|
|
VERSION_TARGET="${INSTALL_BASE}/version.json"
|
|
METADATA_TARGET="${INSTALL_BASE}/metadata.json"
|
|
INDEX_TARGET="${INSTALL_BASE}/skills_index.local.json"
|
|
CONFIG_TARGET="${INSTALL_BASE}/config.json"
|
|
WRAPPER_TARGET="${BIN_DIR}/skillhub"
|
|
LEGACY_WRAPPER_TARGET="${BIN_DIR}/oc-skills"
|
|
|
|
PLUGIN_TARGET_DIR="${HOME}/.openclaw/extensions/skillhub"
|
|
FIND_SKILL_TARGET_DIR="${HOME}/.openclaw/workspace/skills/find-skills"
|
|
PREFERENCE_SKILL_TARGET_DIR="${HOME}/.openclaw/workspace/skills/skillhub-preference"
|
|
|
|
find_openclaw_bin() {
|
|
if command -v openclaw >/dev/null 2>&1; then
|
|
command -v openclaw
|
|
return 0
|
|
fi
|
|
if [[ -x "${HOME}/.local/share/pnpm/openclaw" ]]; then
|
|
echo "${HOME}/.local/share/pnpm/openclaw"
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
install_cli() {
|
|
if ! command -v python3 >/dev/null 2>&1; then
|
|
echo "Error: python3 is required for skillhub." >&2
|
|
exit 1
|
|
fi
|
|
|
|
mkdir -p "${INSTALL_BASE}" "${BIN_DIR}"
|
|
cp "${CLI_SRC_DIR}/skills_store_cli.py" "${CLI_TARGET}"
|
|
cp "${CLI_SRC_DIR}/skills_upgrade.py" "${UPGRADE_MODULE_TARGET}"
|
|
cp "${CLI_SRC_DIR}/version.json" "${VERSION_TARGET}"
|
|
cp "${CLI_SRC_DIR}/metadata.json" "${METADATA_TARGET}"
|
|
if [[ -f "${CLI_SRC_DIR}/skills_index.local.json" ]]; then
|
|
cp "${CLI_SRC_DIR}/skills_index.local.json" "${INDEX_TARGET}"
|
|
fi
|
|
chmod +x "${CLI_TARGET}"
|
|
|
|
if [[ ! -f "${CONFIG_TARGET}" ]]; then
|
|
cat > "${CONFIG_TARGET}" <<'JSON'
|
|
{
|
|
"self_update_url": "https://skillhub-1388575217.cos.ap-guangzhou.myqcloud.com/version.json"
|
|
}
|
|
JSON
|
|
fi
|
|
|
|
cat > "${WRAPPER_TARGET}" <<'WRAPPER'
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
BASE="${HOME}/.skillhub"
|
|
CLI="${BASE}/skills_store_cli.py"
|
|
|
|
if [[ ! -f "${CLI}" ]]; then
|
|
echo "Error: CLI not found at ${CLI}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
exec python3 "${CLI}" "$@"
|
|
WRAPPER
|
|
|
|
chmod +x "${WRAPPER_TARGET}"
|
|
|
|
cat > "${LEGACY_WRAPPER_TARGET}" <<'WRAPPER'
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
exec "${HOME}/.local/bin/skillhub" "$@"
|
|
WRAPPER
|
|
|
|
chmod +x "${LEGACY_WRAPPER_TARGET}"
|
|
}
|
|
|
|
set_workspace_skills_preference() {
|
|
local enabled="$1"
|
|
if ! command -v python3 >/dev/null 2>&1; then
|
|
echo "Warn: python3 not found; cannot persist skills preference." >&2
|
|
return 0
|
|
fi
|
|
|
|
python3 - "$CONFIG_TARGET" "$enabled" <<'PY'
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
config_path = Path(sys.argv[1]).expanduser()
|
|
enabled = sys.argv[2].strip().lower() == "true"
|
|
default_update_url = "https://skillhub-1388575217.cos.ap-guangzhou.myqcloud.com/version.json"
|
|
|
|
raw = {}
|
|
if config_path.exists():
|
|
try:
|
|
loaded = json.loads(config_path.read_text(encoding="utf-8"))
|
|
if isinstance(loaded, dict):
|
|
raw = loaded
|
|
except Exception:
|
|
raw = {}
|
|
|
|
if not isinstance(raw.get("self_update_url"), str) or not raw["self_update_url"].strip():
|
|
raw["self_update_url"] = default_update_url
|
|
raw["install_workspace_skills"] = enabled
|
|
|
|
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
config_path.write_text(json.dumps(raw, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
|
PY
|
|
}
|
|
|
|
install_plugin() {
|
|
mkdir -p "${PLUGIN_TARGET_DIR}"
|
|
cp "${PLUGIN_SRC_DIR}/index.ts" "${PLUGIN_TARGET_DIR}/index.ts"
|
|
cp "${PLUGIN_SRC_DIR}/openclaw.plugin.json" "${PLUGIN_TARGET_DIR}/openclaw.plugin.json"
|
|
}
|
|
|
|
install_skill() {
|
|
local find_skill_src="${SKILL_SRC_DIR}/SKILL.md"
|
|
local preference_skill_src="${SKILL_SRC_DIR}/SKILL.skillhub-preference.md"
|
|
local installed=0
|
|
|
|
if [[ -f "${find_skill_src}" ]]; then
|
|
mkdir -p "${FIND_SKILL_TARGET_DIR}"
|
|
cp "${find_skill_src}" "${FIND_SKILL_TARGET_DIR}/SKILL.md"
|
|
installed=1
|
|
else
|
|
echo "Warn: find-skills source not found at ${find_skill_src}; skipped." >&2
|
|
fi
|
|
|
|
if [[ -f "${preference_skill_src}" ]]; then
|
|
mkdir -p "${PREFERENCE_SKILL_TARGET_DIR}"
|
|
cp "${preference_skill_src}" "${PREFERENCE_SKILL_TARGET_DIR}/SKILL.md"
|
|
installed=1
|
|
else
|
|
echo "Warn: skillhub-preference source not found at ${preference_skill_src}; skipped." >&2
|
|
fi
|
|
|
|
if [[ "${installed}" -ne 1 ]]; then
|
|
echo "Warn: no skill templates installed." >&2
|
|
fi
|
|
}
|
|
|
|
configure_plugin() {
|
|
local openclaw_bin
|
|
if ! openclaw_bin="$(find_openclaw_bin)"; then
|
|
echo "Warn: openclaw not found on PATH; skipped plugin config." >&2
|
|
return 0
|
|
fi
|
|
|
|
"${openclaw_bin}" config set plugins.entries.skillhub.enabled true
|
|
"${openclaw_bin}" config set plugins.entries.skillhub.config.primaryCli 'skillhub'
|
|
"${openclaw_bin}" config set plugins.entries.skillhub.config.fallbackCli 'clawhub'
|
|
"${openclaw_bin}" config set plugins.entries.skillhub.config.primaryLabel 'cn-optimized'
|
|
"${openclaw_bin}" config set plugins.entries.skillhub.config.fallbackLabel 'public-registry'
|
|
}
|
|
|
|
disable_plugin_if_present() {
|
|
local openclaw_bin
|
|
if ! openclaw_bin="$(find_openclaw_bin)"; then
|
|
echo "Warn: openclaw not found on PATH; skipped plugin disable." >&2
|
|
return 0
|
|
fi
|
|
|
|
# Remove the whole config entry to avoid OpenClaw warning:
|
|
# "plugin disabled (not in allowlist) but config is present".
|
|
if ! "${openclaw_bin}" config unset plugins.entries.skillhub >/dev/null 2>&1; then
|
|
echo "Info: skillhub plugin config entry not found or already removed; skip disable."
|
|
fi
|
|
}
|
|
|
|
restart_gateway_if_needed() {
|
|
if [[ "${RESTART_GATEWAY}" -ne 1 ]]; then
|
|
return 0
|
|
fi
|
|
|
|
local openclaw_bin
|
|
if ! openclaw_bin="$(find_openclaw_bin)"; then
|
|
echo "Warn: openclaw not found on PATH; skipped gateway restart." >&2
|
|
return 0
|
|
fi
|
|
|
|
nohup "${openclaw_bin}" gateway run --bind loopback --port 18789 --force >/tmp/openclaw-gateway.log 2>&1 &
|
|
}
|
|
|
|
if [[ "${MODE}" == "all" || "${MODE}" == "cli" ]]; then
|
|
install_cli
|
|
fi
|
|
|
|
if [[ "${SKILLS_PREF}" == "off" ]]; then
|
|
set_workspace_skills_preference false
|
|
elif [[ "${SKILLS_PREF}" == "on" ]]; then
|
|
set_workspace_skills_preference true
|
|
fi
|
|
|
|
if [[ "${MODE}" == "all" || "${MODE}" == "skill" ]]; then
|
|
if [[ "${SKILLS_PREF}" != "off" ]]; then
|
|
install_skill
|
|
else
|
|
echo "Info: skipped workspace skills installation by --no-skills."
|
|
fi
|
|
disable_plugin_if_present
|
|
fi
|
|
|
|
if [[ "${MODE}" == "plugin" ]]; then
|
|
install_plugin
|
|
configure_plugin
|
|
fi
|
|
|
|
restart_gateway_if_needed
|
|
|
|
echo "Install complete."
|
|
echo " mode: ${MODE}"
|
|
if [[ "${MODE}" == "all" || "${MODE}" == "cli" ]]; then
|
|
echo " cli: ${WRAPPER_TARGET}"
|
|
if [[ -f "${INDEX_TARGET}" ]]; then
|
|
echo " index: ${INDEX_TARGET}"
|
|
fi
|
|
fi
|
|
if [[ "${MODE}" == "all" || "${MODE}" == "skill" ]]; then
|
|
if [[ "${SKILLS_PREF}" != "off" ]]; then
|
|
echo " skill: ${FIND_SKILL_TARGET_DIR}/SKILL.md"
|
|
echo " skill: ${PREFERENCE_SKILL_TARGET_DIR}/SKILL.md"
|
|
else
|
|
echo " skill: skipped (--no-skills)"
|
|
fi
|
|
fi
|
|
if [[ "${MODE}" == "plugin" ]]; then
|
|
echo " plugin: ${PLUGIN_TARGET_DIR}"
|
|
fi
|
|
echo
|
|
echo "Quick check:"
|
|
if [[ "${MODE}" == "all" || "${MODE}" == "cli" ]]; then
|
|
echo " skillhub search calendar"
|
|
fi
|
|
if [[ "${MODE}" == "all" || "${MODE}" == "skill" ]]; then
|
|
if [[ "${SKILLS_PREF}" != "off" ]]; then
|
|
echo " test -f ${FIND_SKILL_TARGET_DIR}/SKILL.md && echo find-skills-installed"
|
|
echo " test -f ${PREFERENCE_SKILL_TARGET_DIR}/SKILL.md && echo skillhub-preference-installed"
|
|
else
|
|
echo " skills install skipped by --no-skills"
|
|
fi
|
|
fi
|
|
if [[ "${MODE}" == "plugin" ]]; then
|
|
echo " If you use OpenClaw: openclaw plugins list | grep skillhub"
|
|
fi
|