Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
348 changes: 348 additions & 0 deletions install-opencode.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,348 @@
#!/usr/bin/env bash
set -euo pipefail

# Superpowers OpenCode Installer
# Syncs agents, commands, and skills to OpenCode config directory

# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# Calculate checksum for a file
calculate_checksum() {
local file="$1"
if [[ -f "$file" ]]; then
if command -v sha256sum &> /dev/null; then
sha256sum "$file" | awk '{print $1}'
else
shasum -a 256 "$file" | awk '{print $1}'
fi
else
echo ""
fi
}

# Get script directory (where superpowers is located)
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# Default target directory (using test directory for safe development)
DEFAULT_TARGET="$HOME/.config/opencode"

echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " Superpowers OpenCode Installer"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "This will sync superpowers content to your OpenCode config:"
echo " • agents/ → agent/"
echo " • commands/ → command/"
echo " • skills/ → skills/"
echo ""

# Prompt for target directory
read -p "Install to ${DEFAULT_TARGET}? [Y/n] " -n 1 -r
echo ""

if [[ $REPLY =~ ^[Nn]$ ]]; then
read -p "Enter target directory: " TARGET_DIR
TARGET_DIR="${TARGET_DIR/#\~/$HOME}" # Expand tilde
else
TARGET_DIR="$DEFAULT_TARGET"
fi

# Verify target directory
if [[ ! -d "$TARGET_DIR" ]]; then
echo -e "${YELLOW}Warning: Directory does not exist: ${TARGET_DIR}${NC}"
read -p "Create it? [Y/n] " -n 1 -r
echo ""
if [[ $REPLY =~ ^[Nn]$ ]]; then
echo -e "${RED}Aborted.${NC}"
exit 1
else
mkdir -p "$TARGET_DIR"
echo -e "${GREEN}✓ Created ${TARGET_DIR}${NC}"
fi
fi

echo ""
echo -e "${GREEN}Target directory: ${TARGET_DIR}${NC}"
echo ""

# Phase 1: Collection
echo -e "${BLUE}Phase 1: Scanning files...${NC}"
echo ""

# Create temp file to store file metadata
TEMP_FILE=$(mktemp)
trap "rm -f $TEMP_FILE" EXIT
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix cumulative trap statements and use single quotes to delay variable expansion.

The trap statement at line 79 is replaced (not cumulative) at line 138, causing $TEMP_FILE cleanup to be lost. Additionally, Shellcheck warns (SC2064) that variables should be quoted to prevent expansion at definition time rather than exit time.

Each trap statement should use single quotes and include all cleanup files:

- trap "rm -f $TEMP_FILE" EXIT
+ trap 'rm -f "$TEMP_FILE"' EXIT

Then at line 138, replace (not supplement) with all files:

- trap "rm -f $TEMP_FILE $TEMP_ACTIONS" EXIT
+ trap 'rm -f "$TEMP_FILE" "$TEMP_ACTIONS"' EXIT

And at line 203:

- trap "rm -f $TEMP_FILE $TEMP_NEW $TEMP_OVERWRITE $TEMP_SKIP" EXIT
+ trap 'rm -f "$TEMP_FILE" "$TEMP_NEW" "$TEMP_OVERWRITE" "$TEMP_SKIP"' EXIT

Also applies to: 138-138, 203-203

🧰 Tools
🪛 Shellcheck (0.11.0)

[warning] 79-79: Use single quotes, otherwise this expands now rather than when signalled.

(SC2064)


# Process each directory mapping
scan_directory() {
local source_name="$1"
local target_name="$2"
local source_dir="$SCRIPT_DIR/$source_name"
local target_subdir="$TARGET_DIR/$target_name"

if [[ ! -d "$source_dir" ]]; then
echo -e "${YELLOW}⚠ Skipping ${source_name}/ (not found)${NC}"
return
fi

echo "Scanning ${source_name}/ ..."

# Find all files recursively
while IFS= read -r -d '' source_file; do
# Get relative path from source directory
local rel_path="${source_file#$source_dir/}"
local target_file="$target_subdir/$rel_path"

# Determine action needed
local action
if [[ ! -f "$target_file" ]]; then
action="new"
else
# File exists - check if changed
local source_checksum=$(calculate_checksum "$source_file")
local target_checksum=$(calculate_checksum "$target_file")

if [[ "$source_checksum" == "$target_checksum" ]]; then
action="skip"
else
action="prompt"
fi
fi

# Store: action|source_file|target_file
echo "${action}|${source_file}|${target_file}" >> "$TEMP_FILE"
done < <(find "$source_dir" -type f -print0)
}

# Scan each source directory
scan_directory "agents" "agent"
scan_directory "commands" "command"
scan_directory "skills" "skills"

echo ""
echo -e "${GREEN}✓ Scan complete${NC}"
echo ""

# Phase 2: Collect user decisions for changed files
echo -e "${BLUE}Phase 2: Resolving conflicts...${NC}"
echo ""

CONFLICT_COUNT=0
# Create a temp file for updated actions
TEMP_ACTIONS=$(mktemp)
trap "rm -f $TEMP_FILE $TEMP_ACTIONS" EXIT

while IFS='|' read -r action source_file target_file; do
if [[ "$action" == "prompt" ]]; then
CONFLICT_COUNT=$((CONFLICT_COUNT + 1))

# Get relative path for display
rel_path="${target_file#$TARGET_DIR/}"

echo -e "${YELLOW}File changed: ${rel_path}${NC}"
echo " Source: ${source_file}"
echo " Target: ${target_file}"

while true; do
read -p "[O]verwrite or [S]kip? " -n 1 -r
echo ""

case $REPLY in
[Oo])
action="overwrite"
echo -e "${GREEN} → Will overwrite${NC}"
break
;;
[Ss])
action="skip"
echo -e "${BLUE} → Will skip${NC}"
break
;;
*)
echo " Invalid choice. Please enter O or S."
;;
esac
done
echo ""
fi

# Write updated action to temp file
echo "${action}|${source_file}|${target_file}" >> "$TEMP_ACTIONS"
done < "$TEMP_FILE"

# Replace temp file with updated actions
mv "$TEMP_ACTIONS" "$TEMP_FILE"

if [[ $CONFLICT_COUNT -eq 0 ]]; then
echo -e "${GREEN}✓ No conflicts found${NC}"
else
echo -e "${GREEN}✓ Resolved ${CONFLICT_COUNT} conflict(s)${NC}"
fi
echo ""

# Phase 3: Display summary
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo -e "${BLUE}Summary of planned changes:${NC}"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""

# Count actions
NEW_COUNT=0
OVERWRITE_COUNT=0
SKIP_COUNT=0
Comment on lines +195 to +197
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Initialize SKIP_COUNT variable with other count variables.

SKIP_COUNT is only assigned during the Phase 3 loop, while NEW_COUNT and OVERWRITE_COUNT are properly initialized beforehand. With set -u enabled (line 2), uninitialized variables cause errors. For consistency and safety, initialize SKIP_COUNT with the others:

  # Count actions
  NEW_COUNT=0
  OVERWRITE_COUNT=0
+ SKIP_COUNT=0

And remove the implicit initialization at line 219:

- SKIP_COUNT=$((SKIP_COUNT + 1))
+ SKIP_COUNT=$((SKIP_COUNT + 1))  # Now explicitly initialized above

Also applies to: 219-219

🤖 Prompt for AI Agents
In install-opencode.sh around lines 195 to 197, SKIP_COUNT is not initialized
alongside NEW_COUNT and OVERWRITE_COUNT which causes failures under set -u; add
SKIP_COUNT=0 to that block to initialize it, and remove the later implicit
initialization at line 219 so the variable is only set once at the top.


# Create temp files for each category
TEMP_NEW=$(mktemp)
TEMP_OVERWRITE=$(mktemp)
TEMP_SKIP=$(mktemp)
trap "rm -f $TEMP_FILE $TEMP_NEW $TEMP_OVERWRITE $TEMP_SKIP" EXIT

while IFS='|' read -r action source_file target_file; do
# Get relative path for display
rel_path="${target_file#$TARGET_DIR/}"

case "$action" in
new)
NEW_COUNT=$((NEW_COUNT + 1))
echo "$rel_path" >> "$TEMP_NEW"
;;
overwrite)
OVERWRITE_COUNT=$((OVERWRITE_COUNT + 1))
echo "$rel_path" >> "$TEMP_OVERWRITE"
;;
skip)
SKIP_COUNT=$((SKIP_COUNT + 1))
echo "$rel_path" >> "$TEMP_SKIP"
;;
esac
done < "$TEMP_FILE"

# Display new files
if [[ $NEW_COUNT -gt 0 ]]; then
echo -e "${GREEN}Will copy (${NEW_COUNT} new files):${NC}"
sort "$TEMP_NEW" | while read -r rel_path; do
echo " ✓ $rel_path"
done
echo ""
fi

# Display overwrites
if [[ $OVERWRITE_COUNT -gt 0 ]]; then
echo -e "${YELLOW}Will overwrite (${OVERWRITE_COUNT} files):${NC}"
sort "$TEMP_OVERWRITE" | while read -r rel_path; do
echo " ⚠ $rel_path"
done
echo ""
fi

# Display skips
if [[ $SKIP_COUNT -gt 0 ]]; then
echo -e "${BLUE}Will skip (${SKIP_COUNT} files - keeping your version):${NC}"
sort "$TEMP_SKIP" | while read -r rel_path; do
echo " → $rel_path"
done
echo ""
fi

# Check for opencode.json
if [[ -f "$TARGET_DIR/opencode.json" ]] || [[ -f "$TARGET_DIR/opencode.jsonc" ]]; then
config_file="$TARGET_DIR/opencode.json"
[[ -f "$TARGET_DIR/opencode.jsonc" ]] && config_file="$TARGET_DIR/opencode.jsonc"

if ! grep -q "opencode-skills" "$config_file" 2>/dev/null; then
echo -e "${YELLOW}Note: Add \"opencode-skills\" to your plugin array in opencode.json${NC}"
echo ""
fi
else
echo -e "${YELLOW}Note: Run /init in OpenCode to create opencode.json${NC}"
echo ""
fi

# Final confirmation
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
read -p "Proceed with installation? [Y/n] " -n 1 -r
echo ""

if [[ $REPLY =~ ^[Nn]$ ]]; then
echo -e "${RED}Installation cancelled.${NC}"
exit 0
fi

# Phase 4: Execute
echo ""
echo -e "${BLUE}Phase 4: Installing...${NC}"
echo ""

COPIED_COUNT=0
OVERWRITTEN_COUNT=0
ERROR_COUNT=0

while IFS='|' read -r action source_file target_file; do
# Skip files marked as skip
if [[ "$action" == "skip" ]]; then
continue
fi

# Create target directory if needed
target_dir="$(dirname "$target_file")"
if [[ ! -d "$target_dir" ]]; then
mkdir -p "$target_dir" || {
echo -e "${RED}✗ Failed to create directory: ${target_dir}${NC}"
ERROR_COUNT=$((ERROR_COUNT + 1))
continue
}
fi

# Get relative path for display
rel_path="${target_file#$TARGET_DIR/}"

# Copy file
if cp "$source_file" "$target_file"; then
if [[ "$action" == "new" ]]; then
echo -e "${GREEN}✓ Copied: ${rel_path}${NC}"
COPIED_COUNT=$((COPIED_COUNT + 1))
elif [[ "$action" == "overwrite" ]]; then
echo -e "${YELLOW}✓ Overwrote: ${rel_path}${NC}"
OVERWRITTEN_COUNT=$((OVERWRITTEN_COUNT + 1))
fi
else
echo -e "${RED}✗ Failed to copy: ${rel_path}${NC}"
ERROR_COUNT=$((ERROR_COUNT + 1))
fi
done < "$TEMP_FILE"

echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
if [[ $ERROR_COUNT -eq 0 ]]; then
echo -e "${GREEN}✓ Installation complete!${NC}"
echo ""
echo "Copied: ${COPIED_COUNT} files"
echo "Overwrote: ${OVERWRITTEN_COUNT} files"
echo "Skipped: ${SKIP_COUNT} files"
else
echo -e "${RED}Installation completed with errors${NC}"
echo ""
echo "Copied: ${COPIED_COUNT} files"
echo "Overwrote: ${OVERWRITTEN_COUNT} files"
echo "Skipped: ${SKIP_COUNT} files"
echo "Errors: ${ERROR_COUNT} files"
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""

# Display next steps
echo "Next steps:"
echo ""
echo "1. Ensure your opencode.json includes the plugin:"
echo " {\"plugin\": [\"opencode-skills\"]}"
echo ""
echo "2. Restart OpenCode to load the skills"
echo ""
echo "3. (Optional) Use /init in OpenCode to create AGENTS.md"
echo " with custom instructions about the skills system"
echo ""