mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-05-11 23:24:43 +08:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2357a5de9b | ||
|
|
1946c50fc5 | ||
|
|
49f9fcb0e2 |
150
install.sh
150
install.sh
@@ -130,89 +130,90 @@ if [[ "$MIGRATE" == "y" || "$MIGRATE" == "Y" ]]; then
|
||||
echo "2. Remote migration"
|
||||
read -p "Enter your choice (1 or 2): " MIGRATION_TYPE
|
||||
|
||||
if [[ "$MIGRATION_TYPE" == "1" ]]; then
|
||||
echo "Proceeding with local migration..."
|
||||
case "$MIGRATION_TYPE" in
|
||||
1)
|
||||
echo "Proceeding with local migration..."
|
||||
|
||||
# Default paths for local migration
|
||||
SRC_DEFAULT="$HOME/.openclaw/workspace"
|
||||
DST_DEFAULT="$HOME/.clawgo/workspace"
|
||||
SRC="${SRC_DEFAULT}"
|
||||
DST="${DST_DEFAULT}"
|
||||
# Default paths for local migration
|
||||
SRC_DEFAULT="$HOME/.openclaw/workspace"
|
||||
DST_DEFAULT="$HOME/.clawgo/workspace"
|
||||
SRC="${SRC_DEFAULT}"
|
||||
DST="${DST_DEFAULT}"
|
||||
|
||||
# Prompt user about overwriting existing data
|
||||
echo "Warning: Migration will overwrite the contents of $DST"
|
||||
read -p "Are you sure you want to continue? (y/n): " CONFIRM
|
||||
if [[ "$CONFIRM" != "y" && "$CONFIRM" != "Y" ]]; then
|
||||
echo "Migration canceled."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "[INFO] source: $SRC"
|
||||
echo "[INFO] target: $DST"
|
||||
|
||||
mkdir -p "$DST" "$DST/memory"
|
||||
TS="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||
BACKUP_DIR="$DST/.migration-backup-$TS"
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
|
||||
# Backup existing key files if present
|
||||
for f in AGENTS.md SOUL.md USER.md IDENTITY.md TOOLS.md MEMORY.md HEARTBEAT.md; do
|
||||
if [[ -f "$DST/$f" ]]; then
|
||||
cp -a "$DST/$f" "$BACKUP_DIR/$f"
|
||||
# Prompt user about overwriting existing data
|
||||
echo "Warning: Migration will overwrite the contents of $DST"
|
||||
read -p "Are you sure you want to continue? (y/n): " CONFIRM
|
||||
if [[ "$CONFIRM" != "y" && "$CONFIRM" != "Y" ]]; then
|
||||
echo "Migration canceled."
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
if [[ -d "$DST/memory" ]]; then
|
||||
cp -a "$DST/memory" "$BACKUP_DIR/memory" || true
|
||||
fi
|
||||
|
||||
# Migrate core persona/context files
|
||||
for f in AGENTS.md SOUL.md USER.md IDENTITY.md TOOLS.md MEMORY.md HEARTBEAT.md; do
|
||||
if [[ -f "$SRC/$f" ]]; then
|
||||
cp -a "$SRC/$f" "$DST/$f"
|
||||
echo "[OK] migrated $f"
|
||||
fi
|
||||
done
|
||||
echo "[INFO] source: $SRC"
|
||||
echo "[INFO] target: $DST"
|
||||
|
||||
# Merge memory directory
|
||||
if [[ -d "$SRC/memory" ]]; then
|
||||
rsync -a "$SRC/memory/" "$DST/memory/"
|
||||
echo "[OK] migrated memory/"
|
||||
fi
|
||||
mkdir -p "$DST" "$DST/memory"
|
||||
TS="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||
BACKUP_DIR="$DST/.migration-backup-$TS"
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
|
||||
# Optional: sync into embedded workspace template used by clawgo builds
|
||||
echo "[INFO] Syncing embed workspace template..."
|
||||
if [[ -d "$DST" ]]; then
|
||||
mkdir -p "$DST"
|
||||
# Backup existing key files if present
|
||||
for f in AGENTS.md SOUL.md USER.md IDENTITY.md TOOLS.md MEMORY.md HEARTBEAT.md; do
|
||||
if [[ -f "$DST/$f" ]]; then
|
||||
cp -a "$DST/$f" "$DST/$f"
|
||||
cp -a "$DST/$f" "$BACKUP_DIR/$f"
|
||||
fi
|
||||
done
|
||||
if [[ -d "$DST/memory" ]]; then
|
||||
mkdir -p "$DST/memory"
|
||||
rsync -a "$DST/memory/" "$DST/memory/"
|
||||
cp -a "$DST/memory" "$BACKUP_DIR/memory" || true
|
||||
fi
|
||||
echo "[OK] synced embed workspace template"
|
||||
fi
|
||||
|
||||
echo "[DONE] migration complete"
|
||||
# Migrate core persona/context files
|
||||
for f in AGENTS.md SOUL.md USER.md IDENTITY.md TOOLS.md MEMORY.md HEARTBEAT.md; do
|
||||
if [[ -f "$SRC/$f" ]]; then
|
||||
cp -a "$SRC/$f" "$DST/$f"
|
||||
echo "[OK] migrated $f"
|
||||
fi
|
||||
done
|
||||
|
||||
elif [[ "$MIGRATION_TYPE" == "2" ]]; then
|
||||
echo "Proceeding with remote migration..."
|
||||
# Merge memory directory
|
||||
if [[ -d "$SRC/memory" ]]; then
|
||||
rsync -a "$SRC/memory/" "$DST/memory/"
|
||||
echo "[OK] migrated memory/"
|
||||
fi
|
||||
|
||||
read -p "Enter remote host (e.g., user@hostname): " REMOTE_HOST
|
||||
read -p "Enter remote port (default 22): " REMOTE_PORT
|
||||
REMOTE_PORT="${REMOTE_PORT:-22}"
|
||||
read -sp "Enter remote password: " REMOTE_PASS
|
||||
echo
|
||||
# Optional: sync into embedded workspace template used by clawgo builds
|
||||
echo "[INFO] Syncing embed workspace template..."
|
||||
if [[ -d "$DST" ]]; then
|
||||
mkdir -p "$DST"
|
||||
for f in AGENTS.md SOUL.md USER.md IDENTITY.md TOOLS.md MEMORY.md HEARTBEAT.md; do
|
||||
if [[ -f "$DST/$f" ]]; then
|
||||
cp -a "$DST/$f" "$DST/$f"
|
||||
fi
|
||||
done
|
||||
if [[ -d "$DST/memory" ]]; then
|
||||
mkdir -p "$DST/memory"
|
||||
rsync -a "$DST/memory/" "$DST/memory/"
|
||||
fi
|
||||
echo "[OK] synced embed workspace template"
|
||||
fi
|
||||
|
||||
# Create a temporary SSH key for non-interactive SSH authentication (assuming sshpass is installed)
|
||||
SSH_KEY=$(mktemp)
|
||||
sshpass -p "$REMOTE_PASS" ssh-copy-id -i "$SSH_KEY" "$REMOTE_HOST -p $REMOTE_PORT"
|
||||
echo "[DONE] migration complete"
|
||||
;;
|
||||
2)
|
||||
echo "Proceeding with remote migration..."
|
||||
|
||||
# Prepare migration script
|
||||
MIGRATION_SCRIPT="$TMPDIR/openclaw2clawgo.sh"
|
||||
cat << 'EOF' > "$MIGRATION_SCRIPT"
|
||||
read -p "Enter remote host (e.g., user@hostname): " REMOTE_HOST
|
||||
read -p "Enter remote port (default 22): " REMOTE_PORT
|
||||
REMOTE_PORT="${REMOTE_PORT:-22}"
|
||||
read -sp "Enter remote password: " REMOTE_PASS
|
||||
echo
|
||||
|
||||
# Create a temporary SSH key for non-interactive SSH authentication (assuming sshpass is installed)
|
||||
SSH_KEY=$(mktemp)
|
||||
sshpass -p "$REMOTE_PASS" ssh-copy-id -i "$SSH_KEY" -p "$REMOTE_PORT" "$REMOTE_HOST"
|
||||
|
||||
# Prepare migration script
|
||||
MIGRATION_SCRIPT="$TMPDIR/openclaw2clawgo.sh"
|
||||
cat << 'EOF' > "$MIGRATION_SCRIPT"
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
@@ -256,15 +257,16 @@ fi
|
||||
echo "[DONE] migration complete"
|
||||
EOF
|
||||
|
||||
# Copy migration script to remote server and execute it
|
||||
sshpass -p "$REMOTE_PASS" scp -P "$REMOTE_PORT" "$MIGRATION_SCRIPT" "$REMOTE_HOST:/tmp/openclaw2clawgo.sh"
|
||||
sshpass -p "$REMOTE_PASS" ssh -p "$REMOTE_PORT" "$REMOTE_HOST" "bash /tmp/openclaw2clawgo.sh"
|
||||
# Copy migration script to remote server and execute it
|
||||
sshpass -p "$REMOTE_PASS" scp -P "$REMOTE_PORT" "$MIGRATION_SCRIPT" "$REMOTE_HOST:/tmp/openclaw2clawgo.sh"
|
||||
sshpass -p "$REMOTE_PASS" ssh -p "$REMOTE_PORT" "$REMOTE_HOST" "bash /tmp/openclaw2clawgo.sh"
|
||||
|
||||
echo "[INFO] Remote migration completed."
|
||||
|
||||
else
|
||||
echo "Invalid choice. Skipping migration."
|
||||
fi
|
||||
echo "[INFO] Remote migration completed."
|
||||
;;
|
||||
*)
|
||||
echo "Invalid choice. Skipping migration."
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
echo "Cleaning up..."
|
||||
|
||||
@@ -795,7 +795,9 @@ func (s *RegistryServer) handleWebUISkills(w http.ResponseWriter, r *http.Reques
|
||||
seenDirs := map[string]struct{}{}
|
||||
seenSkills := map[string]struct{}{}
|
||||
items := make([]skillItem, 0)
|
||||
checkUpdates := strings.TrimSpace(r.URL.Query().Get("check_updates")) != "0"
|
||||
// Default off to avoid hammering clawhub search API on each UI refresh.
|
||||
// Enable explicitly with ?check_updates=1 when needed.
|
||||
checkUpdates := strings.TrimSpace(r.URL.Query().Get("check_updates")) == "1"
|
||||
|
||||
for _, dir := range candDirs {
|
||||
dir = strings.TrimSpace(dir)
|
||||
@@ -914,11 +916,28 @@ func (s *RegistryServer) handleWebUISkills(w http.ResponseWriter, r *http.Reques
|
||||
http.Error(w, "clawhub is not installed. please install clawhub first.", http.StatusPreconditionFailed)
|
||||
return
|
||||
}
|
||||
cmd := exec.CommandContext(r.Context(), clawhubPath, "install", name)
|
||||
ignoreSuspicious := false
|
||||
switch v := body["ignore_suspicious"].(type) {
|
||||
case bool:
|
||||
ignoreSuspicious = v
|
||||
case string:
|
||||
ignoreSuspicious = strings.EqualFold(strings.TrimSpace(v), "true") || strings.TrimSpace(v) == "1"
|
||||
}
|
||||
args := []string{"install", name}
|
||||
if ignoreSuspicious {
|
||||
args = append(args, "--force")
|
||||
}
|
||||
cmd := exec.CommandContext(r.Context(), clawhubPath, args...)
|
||||
cmd.Dir = strings.TrimSpace(s.workspacePath)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("install failed: %v\n%s", err, string(out)), http.StatusInternalServerError)
|
||||
outText := string(out)
|
||||
lower := strings.ToLower(outText)
|
||||
if strings.Contains(lower, "rate limit exceeded") || strings.Contains(lower, "too many requests") {
|
||||
http.Error(w, fmt.Sprintf("clawhub rate limit exceeded. please retry later or configure auth token.\n%s", outText), http.StatusTooManyRequests)
|
||||
return
|
||||
}
|
||||
http.Error(w, fmt.Sprintf("install failed: %v\n%s", err, outText), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(map[string]interface{}{"ok": true, "installed": name, "output": string(out)})
|
||||
|
||||
@@ -175,6 +175,9 @@ const resources = {
|
||||
skillsInstallFailedMessage: 'Failed to install clawhub',
|
||||
skillsInstallDoneTitle: 'Install complete',
|
||||
skillsInstallDoneMessage: 'clawhub is installed. You can continue installing skills.',
|
||||
skillsInstallingSkill: 'Installing skill...',
|
||||
skillsInstallSkillDoneTitle: 'Skill installed',
|
||||
skillsInstallSkillDoneMessage: 'Skill "{{name}}" installed successfully.',
|
||||
skillsAddTitle: 'Add Skill',
|
||||
skillsAddMessage: 'Upload skill archive (.zip / .tar.gz / .tgz / .tar). It will be extracted into skills folder and archive will be removed.',
|
||||
skillsSelectArchive: 'Select archive',
|
||||
@@ -186,6 +189,7 @@ const resources = {
|
||||
skillsImportDoneMessage: 'Skill imported successfully.',
|
||||
skillsFileSaved: 'Skill file saved successfully.',
|
||||
skillsNamePlaceholder: 'skill name',
|
||||
skillsIgnoreSuspicious: 'Ignore suspicious warning (use --force)',
|
||||
skillsClawhubNotFound: 'clawhub not found',
|
||||
skillsClawhubStatus: 'clawhub',
|
||||
skillsAdd: 'Add Skill',
|
||||
@@ -474,6 +478,9 @@ const resources = {
|
||||
skillsInstallFailedMessage: '安装 clawhub 失败',
|
||||
skillsInstallDoneTitle: '安装完成',
|
||||
skillsInstallDoneMessage: 'clawhub 已安装,可继续安装技能。',
|
||||
skillsInstallingSkill: '正在安装技能...',
|
||||
skillsInstallSkillDoneTitle: '技能安装完成',
|
||||
skillsInstallSkillDoneMessage: '技能 "{{name}}" 安装成功。',
|
||||
skillsAddTitle: '添加技能',
|
||||
skillsAddMessage: '请上传技能压缩包(.zip / .tar.gz / .tgz / .tar)。上传后将自动解压到 skills 目录,并删除上传压缩包。',
|
||||
skillsSelectArchive: '选择压缩包',
|
||||
@@ -485,6 +492,7 @@ const resources = {
|
||||
skillsImportDoneMessage: '技能导入成功。',
|
||||
skillsFileSaved: '技能文件保存成功。',
|
||||
skillsNamePlaceholder: '技能名',
|
||||
skillsIgnoreSuspicious: '忽略可疑告警(使用 --force)',
|
||||
skillsClawhubNotFound: '未找到 clawhub',
|
||||
skillsClawhubStatus: 'clawhub',
|
||||
skillsAdd: '添加技能',
|
||||
|
||||
@@ -10,6 +10,8 @@ const Skills: React.FC = () => {
|
||||
const { skills, refreshSkills, q, clawhubInstalled, clawhubPath } = useAppContext();
|
||||
const ui = useUI();
|
||||
const [installName, setInstallName] = useState('');
|
||||
const [installingSkill, setInstallingSkill] = useState(false);
|
||||
const [ignoreSuspicious, setIgnoreSuspicious] = useState(false);
|
||||
const qp = (k: string, v: string) => `${q}${q ? '&' : '?'}${k}=${encodeURIComponent(v)}`;
|
||||
|
||||
const [isFileModalOpen, setIsFileModalOpen] = useState(false);
|
||||
@@ -47,35 +49,49 @@ const Skills: React.FC = () => {
|
||||
});
|
||||
const text = await r.text();
|
||||
if (!r.ok) {
|
||||
ui.hideLoading();
|
||||
await ui.notify({ title: t('skillsInstallFailedTitle'), message: text || t('skillsInstallFailedMessage') });
|
||||
return false;
|
||||
}
|
||||
ui.hideLoading();
|
||||
await ui.notify({ title: t('skillsInstallDoneTitle'), message: t('skillsInstallDoneMessage') });
|
||||
await refreshSkills();
|
||||
return true;
|
||||
} finally {
|
||||
// loading is explicitly closed before notify, keep this as fallback.
|
||||
ui.hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
async function installSkill() {
|
||||
if (installingSkill) return;
|
||||
const name = installName.trim();
|
||||
if (!name) return;
|
||||
|
||||
const ready = await installClawHubIfNeeded();
|
||||
if (!ready) return;
|
||||
setInstallingSkill(true);
|
||||
ui.showLoading(t('skillsInstallingSkill'));
|
||||
try {
|
||||
const ready = await installClawHubIfNeeded();
|
||||
if (!ready) return;
|
||||
|
||||
const r = await fetch(`/webui/api/skills${q}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ action: 'install', name }),
|
||||
});
|
||||
if (!r.ok) {
|
||||
await ui.notify({ title: t('requestFailed'), message: await r.text() });
|
||||
return;
|
||||
const r = await fetch(`/webui/api/skills${q}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ action: 'install', name, ignore_suspicious: ignoreSuspicious }),
|
||||
});
|
||||
if (!r.ok) {
|
||||
ui.hideLoading();
|
||||
await ui.notify({ title: t('requestFailed'), message: await r.text() });
|
||||
return;
|
||||
}
|
||||
setInstallName('');
|
||||
await refreshSkills();
|
||||
ui.hideLoading();
|
||||
await ui.notify({ title: t('skillsInstallSkillDoneTitle'), message: t('skillsInstallSkillDoneMessage', { name }) });
|
||||
} finally {
|
||||
ui.hideLoading();
|
||||
setInstallingSkill(false);
|
||||
}
|
||||
setInstallName('');
|
||||
await refreshSkills();
|
||||
}
|
||||
|
||||
async function onAddSkillClick() {
|
||||
@@ -173,8 +189,17 @@ const Skills: React.FC = () => {
|
||||
<div className="flex items-center justify-between gap-3 flex-wrap">
|
||||
<h1 className="text-2xl font-semibold tracking-tight">{t('skills')}</h1>
|
||||
<div className="flex items-center gap-2">
|
||||
<input value={installName} onChange={(e) => setInstallName(e.target.value)} placeholder={t('skillsNamePlaceholder')} className="px-3 py-2 bg-zinc-950 border border-zinc-800 rounded-lg text-sm" />
|
||||
<button onClick={installSkill} className="px-3 py-2 bg-emerald-600 hover:bg-emerald-500 text-white rounded-lg text-sm font-medium">{t('install')}</button>
|
||||
<input disabled={installingSkill} value={installName} onChange={(e) => setInstallName(e.target.value)} placeholder={t('skillsNamePlaceholder')} className="px-3 py-2 bg-zinc-950 border border-zinc-800 rounded-lg text-sm disabled:opacity-60" />
|
||||
<button disabled={installingSkill} onClick={installSkill} className="px-3 py-2 bg-emerald-600 hover:bg-emerald-500 disabled:opacity-60 disabled:cursor-not-allowed text-white rounded-lg text-sm font-medium">{installingSkill ? t('loading') : t('install')}</button>
|
||||
<label className="flex items-center gap-2 text-xs text-zinc-400">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={ignoreSuspicious}
|
||||
disabled={installingSkill}
|
||||
onChange={(e) => setIgnoreSuspicious(e.target.checked)}
|
||||
/>
|
||||
{t('skillsIgnoreSuspicious')}
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={`text-xs px-2 py-1 rounded-md border ${clawhubInstalled ? 'text-emerald-300 border-emerald-700/50 bg-emerald-900/20' : 'text-amber-300 border-amber-700/50 bg-amber-900/20'}`} title={clawhubPath || t('skillsClawhubNotFound')}>
|
||||
|
||||
Reference in New Issue
Block a user