#!/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 < 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 "$ROOT/dsh/subagent-models.yml" ]; then echo "ok: subagent overlay ($ROOT/dsh/subagent-models.yml)" else echo "WARN: subagent overlay missing; SDD runs on the session model at depth 1" >&2 fi local patch found=0 for patch in "$DSH_HOME"/profiles/*/cordis.patch.yml; do [ -f "$patch" ] || continue if grep -qs 'maxDepth: 2' "$patch"; then found=1 break fi done if [ "$found" = 1 ]; then echo "ok: subagent maxDepth=2 ($patch)" else echo "WARN: subagent maxDepth is not 2 in any profile patch; SDD mode 2 is unavailable" >&2 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 < 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"