claude-harness/dsh/install.sh
2026-10-24 16:30:25 +04:00

242 lines
7.0 KiB
Bash
Executable File

#!/usr/bin/env bash
# Install the DeepSeek Harness side of claude-harness.
#
# This script never installs the Cordis bundle into a profile: the Desktop
# profile is Electron-managed, so the bundle goes through the GUI Plugins page
# or the model-facing `plugin_manager` tool. The script prepares the global
# instructions, the user skill root, and the role files, then prints the bundle
# path.
#
# Usage:
# bash dsh/install.sh --check
# bash dsh/install.sh --global
# bash dsh/install.sh --skills
# bash dsh/install.sh --roles
# bash dsh/install.sh --all
#
# Run it from a terminal, not from inside a DSH session: --global, --skills and
# --roles write outside the session workspace and would require approval.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
BUNDLE="$ROOT/dsh/bundle"
DSH_HOME="${DSH_HOME:-$HOME/.dsh}"
DO_CHECK=0
DO_GLOBAL=0
DO_SKILLS=0
DO_ROLES=0
usage() {
cat <<EOF
Usage: bash dsh/install.sh [--check] [--global] [--skills] [--roles] [--all]
--check verify python3, git, node, hooks, bundle, skills, and roles
--global back up and write \$DSH_HOME/AGENTS.md from global/CLAUDE.md
--skills copy superpowers skills and local dsh/skills into \$DSH_HOME/skills
--roles copy roles/ into \$DSH_HOME/roles
--all --check + --global + --skills + --roles, then print the bundle step
Bundle installation itself is done in DSH:
Plugins -> install a bundle from $BUNDLE
or ask the agent to call plugin_manager install_bundle with that target.
EOF
}
if [ "$#" -eq 0 ]; then
DO_CHECK=1
fi
for arg in "$@"; do
case "$arg" in
--check) DO_CHECK=1 ;;
--global) DO_GLOBAL=1 ;;
--skills) DO_SKILLS=1 ;;
--roles) DO_ROLES=1 ;;
--all) DO_CHECK=1; DO_GLOBAL=1; DO_SKILLS=1; DO_ROLES=1 ;;
-h|--help) usage; exit 0 ;;
*) echo "unknown option: $arg" >&2; usage >&2; exit 2 ;;
esac
done
superpowers_skills() {
local dir
dir="$(ls -d "$HOME"/.claude/plugins/cache/claude-plugins-official/superpowers/*/skills 2>/dev/null | sort -V | tail -1 || true)"
if [ -n "$dir" ] && [ -d "$dir" ]; then
printf '%s\n' "$dir"
return 0
fi
return 1
}
check() {
local failed=0
for cmd in python3 git node; do
if command -v "$cmd" >/dev/null 2>&1; then
echo "ok: $cmd ($("$cmd" --version 2>&1 | head -1))"
else
echo "WARN: $cmd not found" >&2
failed=1
fi
done
if [ -f "$ROOT/hooks/harness_target.py" ]; then
echo "ok: hooks/harness_target.py"
else
echo "FAIL: $ROOT/hooks/harness_target.py is missing" >&2
failed=1
fi
if [ -f "$ROOT/global/CLAUDE.md" ]; then
echo "ok: global/CLAUDE.md"
else
echo "FAIL: $ROOT/global/CLAUDE.md is missing" >&2
failed=1
fi
if [ -f "$BUNDLE/index.js" ] && [ -f "$BUNDLE/cordis.patch.yml" ]; then
echo "ok: bundle $BUNDLE"
else
echo "FAIL: bundle is incomplete: $BUNDLE" >&2
failed=1
fi
if [ -f "$BUNDLE/hooks/guard-main.sh" ] && [ -f "$BUNDLE/hooks/require-plan.sh" ]; then
echo "ok: hook bridge"
else
echo "FAIL: hook bridge is missing (is dsh/bundle/hooks a symlink to ../../hooks?)" >&2
failed=1
fi
if [ -f "$DSH_HOME/skills/using-superpowers/SKILL.md" ]; then
echo "ok: dsh skills ($DSH_HOME/skills)"
elif superpowers_skills >/dev/null 2>&1; then
echo "ok: superpowers source ($(superpowers_skills)); run --skills to copy"
else
echo "WARN: superpowers plugin not found; the bootstrap will be skipped" >&2
fi
if [ -f "$DSH_HOME/skills/role/SKILL.md" ]; then
echo "ok: role skill"
else
echo "WARN: role skill not installed; run --skills" >&2
fi
if [ -f "$DSH_HOME/roles/dev.md" ]; then
echo "ok: roles ($DSH_HOME/roles)"
elif [ -f "$HOME/.claude/roles/dev.md" ]; then
echo "ok: roles (~/.claude/roles; run --roles to copy)"
elif [ -f "$ROOT/roles/dev.md" ]; then
echo "ok: roles ($ROOT/roles; run --roles to install)"
else
echo "WARN: roles not found; /role will fail until --roles is run" >&2
fi
echo "DSH_HOME=$DSH_HOME"
return "$failed"
}
install_global() {
mkdir -p "$DSH_HOME"
local target="$DSH_HOME/AGENTS.md"
if [ -f "$target" ]; then
local backup="$target.bak.$(date +%s)"
cp "$target" "$backup"
echo "backup: $backup"
fi
{
echo "# DeepSeek Harness global instructions"
echo
echo "Installed from $ROOT/global/CLAUDE.md by dsh/install.sh."
echo "The dsh-harness bundle enforces guard-main/require-plan and provides the /role command."
echo "DSH adapter: the model loads roles with skill role; /role is the user-facing command."
echo "DSH adapter: there is no SessionStart role hint; infer the role from the first message."
echo "DSH adapter: role files are ~/.dsh/roles, then ~/.claude/roles, then $ROOT/roles."
echo "DSH adapter: bootstrap prefers ~/.dsh/skills; the ~/.claude cache is a fallback."
echo "DSH adapter: codegraph MCP tools are named mcp__codegraph__codegraph_explore."
echo
cat "$ROOT/global/CLAUDE.md"
} > "$target"
echo "written: $target"
}
install_skills() {
mkdir -p "$DSH_HOME/skills"
local skills
if ! skills="$(superpowers_skills)"; then
echo "WARN: superpowers skills not found; nothing copied" >&2
return 0
fi
local count=0
local dir name dest
for dir in "$skills"/*; do
[ -f "$dir/SKILL.md" ] || continue
name="$(basename "$dir")"
dest="$DSH_HOME/skills/$name"
rm -rf "$dest"
cp -R "$dir" "$dest"
count=$((count + 1))
done
echo "copied $count superpowers skills into $DSH_HOME/skills"
}
install_local_skills() {
local src="$ROOT/dsh/skills"
[ -d "$src" ] || return 0
mkdir -p "$DSH_HOME/skills"
local count=0
local dir name dest
for dir in "$src"/*; do
[ -f "$dir/SKILL.md" ] || continue
name="$(basename "$dir")"
dest="$DSH_HOME/skills/$name"
rm -rf "$dest"
cp -R "$dir" "$dest"
count=$((count + 1))
done
echo "copied $count local skills into $DSH_HOME/skills"
}
install_roles() {
mkdir -p "$DSH_HOME/roles"
local src="$ROOT/roles"
local count=0
local file name
for file in "$src"/*.md; do
[ -f "$file" ] || continue
name="$(basename "$file")"
cp "$file" "$DSH_HOME/roles/$name"
count=$((count + 1))
done
if [ "$count" -eq 0 ]; then
echo "WARN: no role files found in $src" >&2
return 0
fi
echo "installed $count roles into $DSH_HOME/roles"
}
print_bundle_step() {
cat <<EOF
Bundle: $BUNDLE
Install it in the current DSH profile (the Desktop profile is Electron-managed):
- GUI: Plugins -> install a bundle from the directory above;
- or ask the agent to call plugin_manager install_bundle with target "$BUNDLE".
Do not edit \$DSH_HOME/profiles/desktop/package.json or cordis.patch.yml by hand.
Optional: add CodeGraph as an MCP server from dsh/mcp.codegraph.yml.
EOF
}
status=0
if [ "$DO_CHECK" = 1 ]; then
check || status=1
fi
if [ "$DO_GLOBAL" = 1 ]; then
install_global
fi
if [ "$DO_SKILLS" = 1 ]; then
install_skills
install_local_skills
fi
if [ "$DO_ROLES" = 1 ]; then
install_roles
fi
if [ "$DO_GLOBAL" = 1 ] || [ "$DO_SKILLS" = 1 ] || [ "$DO_ROLES" = 1 ]; then
print_bundle_step
fi
exit "$status"