diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ef5cc1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +# Skills repository .gitignore + +# Nix +.direnv/ +result +result-* + +# Editor/IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Logs +*.log + +# Temporary files +*.tmp +*.bak + +# Environment +.env +.env.local diff --git a/AGENTS.md b/AGENTS.md index d5377e2..b0d0a0a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -64,3 +64,10 @@ ln -s $(pwd)/skills/ ~/.claude/skills/-test **Deployment tool**: `./bin/deploy-skill.sh ` (copies to dotfiles, shows Nix config) **Note on Agents**: OpenCode agents (Build/Plan/custom) use Tab to switch. Agents are modes/personalities, not skills. Skills work with all agents. Per-agent skill filtering not supported. + +## Active Technologies +- Bash (no specific version requirement - standard POSIX + bash extensions) + `jq`, `curl`, `nix-prefetch-url`, `sed`/`awk`, GitHub API (002-update-opencode) +- Filesystem - modifies `~/proj/dotfiles/pkgs/opencode/default.nix` (002-update-opencode) + +## Recent Changes +- 002-update-opencode: Added Bash (no specific version requirement - standard POSIX + bash extensions) + `jq`, `curl`, `nix-prefetch-url`, `sed`/`awk`, GitHub API diff --git a/skills/update-opencode/README.md b/skills/update-opencode/README.md new file mode 100644 index 0000000..cb1f9fb --- /dev/null +++ b/skills/update-opencode/README.md @@ -0,0 +1,171 @@ +# Update OpenCode Skill + +Automate checking for and applying OpenCode version updates in Nix-based dotfiles. + +## Features + +- **Check versions**: Compare current OpenCode installation against latest GitHub release +- **Auto-update**: Fetch SHA256 hashes and update Nix package definitions +- **Verify installations**: Confirm successful updates after rebuild +- **Version pinning**: Install specific versions for compatibility + +## Installation + +### Global Installation (Recommended) + +1. Copy this skill to your dotfiles: +```bash +cd ~/proj/skills +./bin/deploy-skill.sh update-opencode +``` + +2. Follow displayed instructions to update `modules/ai-skills.nix` in your dotfiles + +3. Rebuild system: +```bash +cd ~/proj/dotfiles +sudo nixos-rebuild switch --flake .# +``` + +4. Verify installation: +```bash +ls ~/.claude/skills/update-opencode +ls ~/.config/opencode/skills/update-opencode +``` + +### Project-Local Installation (Alternative) + +For use in a specific project only: +```bash +mkdir -p .claude/skills .opencode/skills +cp -r ~/proj/skills/skills/update-opencode .claude/skills/ +cp -r ~/proj/skills/skills/update-opencode .opencode/skills/ +``` + +## Prerequisites + +**Required tools:** +- `jq` - JSON parsing +- `curl` - HTTP requests +- `nix-prefetch-url` - Binary hash computation +- `sed` - File editing +- `grep` - Pattern matching + +**Required access:** +- Read/write to dotfiles repository (`~/proj/dotfiles` by default) +- `sudo` permissions for `nixos-rebuild switch` +- Network access to GitHub API + +**Verify prerequisites:** +```bash +command -v jq curl nix-prefetch-url sed grep && echo "✓ All tools available" +``` + +## Usage + +### With AI Agents (Claude Code / OpenCode) + +Simply ask the agent: +- "Check for OpenCode updates" +- "Update OpenCode to latest" +- "Install OpenCode version 1.0.44" + +The agent will use this skill automatically. + +### Manual Usage + +**Check current vs latest version:** +```bash +~/.claude/skills/update-opencode/scripts/check-version.sh +``` + +**Fetch SHA256 for specific version:** +```bash +~/.claude/skills/update-opencode/scripts/fetch-sha256.sh 1.0.51 +``` + +**Update Nix file (dry-run first):** +```bash +~/.claude/skills/update-opencode/scripts/update-nix-file.sh 1.0.51 sha256-ABC... --dry-run +~/.claude/skills/update-opencode/scripts/update-nix-file.sh 1.0.51 sha256-ABC... +``` + +**Rebuild system:** +```bash +cd ~/proj/dotfiles +sudo nixos-rebuild switch --flake .# +``` + +**Verify installation:** +```bash +~/.claude/skills/update-opencode/scripts/verify-update.sh 1.0.51 +``` + +## Configuration + +**Custom dotfiles path:** +```bash +# Set via environment variable +export DOTFILES_PATH=~/my-dotfiles + +# Or pass as argument +./check-version.sh --dotfiles ~/my-dotfiles +./update-nix-file.sh 1.0.51 sha256-ABC... --dotfiles ~/my-dotfiles +``` + +## Troubleshooting + +**"Error: Nix file not found"** +- Verify dotfiles path is correct +- Check that `pkgs/opencode/default.nix` exists + +**"Error: Failed to query GitHub API"** +- Check network connectivity +- Verify GitHub API is reachable: `curl -s https://api.github.com/repos/sst/opencode/releases/latest` +- Check for rate limiting (60 requests/hour unauthenticated) + +**"Error: Failed to fetch or hash"** +- Verify version exists on GitHub releases +- Check network connectivity to GitHub +- Ensure `nix-prefetch-url` is installed + +**"Verification failed: Version mismatch"** +- Rebuild may not have completed successfully +- Check rebuild logs for errors +- Try re-running: `cd ~/proj/dotfiles && sudo nixos-rebuild switch --flake .#` + +**"Error: Required command not found"** +- Install missing tools via Nix or system package manager +- For NixOS, add to `environment.systemPackages` in configuration + +## Files + +``` +skills/update-opencode/ +├── SKILL.md # Agent instructions +├── README.md # This file +├── scripts/ +│ ├── check-version.sh # Compare versions +│ ├── fetch-sha256.sh # Fetch SRI hash +│ ├── update-nix-file.sh # Modify Nix file +│ └── verify-update.sh # Verify installation +├── examples/ +│ └── usage-example.sh # Complete workflow demo +└── references/ + └── nix-package-format.md # Nix package structure reference +``` + +## Examples + +See `examples/usage-example.sh` for a complete workflow demonstration. + +## Security + +- All scripts validate input formats before execution +- File modifications are atomic (all-or-nothing) +- No partial updates - failures abort before system changes +- Dry-run mode available for safe preview + +## License + +Part of the AI Skills collection. See repository root for license information. diff --git a/skills/update-opencode/SKILL.md b/skills/update-opencode/SKILL.md new file mode 100644 index 0000000..4818ef9 --- /dev/null +++ b/skills/update-opencode/SKILL.md @@ -0,0 +1,182 @@ +--- +name: update-opencode +description: Check for and apply OpenCode version updates in Nix-based dotfiles. Use when asked to update OpenCode, check OpenCode version, or upgrade OpenCode. +--- + +# Update OpenCode Skill + +This skill automates checking for and applying OpenCode version updates in a Nix-based dotfiles setup. + +## When to Use + +Use this skill when the user requests: +- Check OpenCode version or check for updates +- Update/upgrade OpenCode to latest version +- Install specific OpenCode version +- "Is there a newer version of OpenCode?" + +## Process + +### 1. Check Current vs Latest Version + +Run the version check script: +```bash +cd ~/.claude/skills/update-opencode/scripts +./check-version.sh +``` + +This outputs: +``` +current=X.Y.Z +latest=X.Y.Z +update_available=yes|no +``` + +**Report findings to user** with version numbers and update availability. + +### 2. Apply Update (if user confirms) + +If `update_available=yes`, **ask user for confirmation** before proceeding: +- Explain that this will modify Nix configuration and rebuild the system +- Mention rebuild may take a few minutes +- Ask: "Proceed with update to version X.Y.Z?" + +If user confirms, execute the following steps: + +**Step 2a: Fetch SHA256 hash** +```bash +./fetch-sha256.sh +``` + +This downloads the release and computes the SRI hash (output: `sha256-...`). + +**Step 2b: Update Nix package file** +```bash +./update-nix-file.sh +``` + +For safety, can use `--dry-run` first to preview changes: +```bash +./update-nix-file.sh --dry-run +``` + +**Step 2c: Trigger system rebuild** +```bash +cd ~/proj/dotfiles +sudo nixos-rebuild switch --flake .#delpad +``` + +This will rebuild the NixOS configuration with the new OpenCode version. + +**Step 2d: Verify installation** +```bash +cd ~/.claude/skills/update-opencode/scripts +./verify-update.sh +``` + +This confirms OpenCode reports the expected version. + +### 3. Install Specific Version + +For version pinning or downgrades: + +```bash +# Fetch hash for specific version +./fetch-sha256.sh 1.0.44 + +# Update and rebuild as above +./update-nix-file.sh 1.0.44 +cd ~/proj/dotfiles && sudo nixos-rebuild switch --flake .#delpad +./verify-update.sh 1.0.44 +``` + +## Requirements + +**Tools:** +- `jq` - JSON parsing for GitHub API +- `curl` - HTTP requests for GitHub API +- `nix-prefetch-url` - Download and hash verification +- `sed` - File modification +- `grep` - Pattern matching + +**Permissions:** +- Read access to `~/proj/dotfiles/pkgs/opencode/default.nix` +- Write access to dotfiles repository +- `sudo` for `nixos-rebuild switch` + +**Network:** +- GitHub API access: `https://api.github.com/repos/sst/opencode/releases` +- GitHub releases: `https://github.com/sst/opencode/releases/download/` + +## Helper Scripts + +**check-version.sh** +- Reads current version from Nix file +- Queries GitHub API for latest release +- Compares versions +- Output: `key=value` pairs + +**fetch-sha256.sh ** +- Downloads OpenCode release for specified version +- Computes SRI hash using `nix-prefetch-url` +- Converts to SRI format (sha256-...) +- Output: SRI hash string + +**update-nix-file.sh [--dry-run]** +- Updates version and sha256 fields in Nix file +- Validates patterns before modifying +- Supports dry-run mode +- Verifies changes after update + +**verify-update.sh ** +- Runs `opencode --version` +- Compares output to expected version +- Exit code 0 on success, 1 on mismatch + +## Error Handling + +**Network failures:** Report clear error, suggest manual GitHub check + +**Missing Nix file:** Report path error, verify dotfiles location + +**Invalid version:** Report format error (expected X.Y.Z) + +**SHA256 fetch failure:** Do not modify files, report download error + +**Rebuild failure:** Report error with logs, suggest rollback or manual intervention + +**Verification failure:** Report version mismatch, suggest re-running rebuild + +## Guidelines + +1. **Always ask confirmation** before triggering system rebuild +2. **Report progress** at each step (fetching hash, updating file, rebuilding) +3. **Handle errors gracefully** - explain what went wrong and suggest fixes +4. **Verify atomicity** - if any step fails, do not proceed to next step +5. **Check prerequisites** - ensure all required tools are installed before starting + +## Examples + +**Example 1: Check for updates** +``` +User: "Check if there's a new OpenCode version" +Agent: *runs check-version.sh* +Agent: "Current version: 1.0.44, Latest: 1.0.51. Update available." +``` + +**Example 2: Apply update** +``` +User: "Update OpenCode to latest" +Agent: *runs check-version.sh* +Agent: "Update available: 1.0.44 → 1.0.51. This will rebuild your system. Proceed?" +User: "Yes" +Agent: *runs fetch-sha256.sh, update-nix-file.sh, rebuild, verify-update.sh* +Agent: "✓ Updated to OpenCode 1.0.51" +``` + +**Example 3: Specific version** +``` +User: "Install OpenCode 1.0.44" +Agent: *fetches hash, updates file, rebuilds, verifies* +Agent: "✓ Installed OpenCode 1.0.44" +``` diff --git a/skills/update-opencode/examples/usage-example.sh b/skills/update-opencode/examples/usage-example.sh new file mode 100755 index 0000000..34ef116 --- /dev/null +++ b/skills/update-opencode/examples/usage-example.sh @@ -0,0 +1,94 @@ +#!/usr/bin/env bash +set -euo pipefail + +# usage-example.sh - Complete workflow demonstration for update-opencode skill +# +# This script demonstrates the full update workflow: +# 1. Check current vs latest version +# 2. Fetch SHA256 for new version +# 3. Update Nix file (dry-run, then actual) +# 4. Trigger system rebuild +# 5. Verify installation +# +# NOTE: This is a demonstration script. In production, steps 3-5 should only +# run after user confirmation due to system rebuild impact. + +SCRIPTS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../scripts" && pwd)" +DOTFILES_PATH="${DOTFILES_PATH:-$HOME/proj/dotfiles}" + +echo "=== OpenCode Update Workflow Demo ===" +echo "" + +# Step 1: Check for updates +echo "Step 1: Checking current version vs latest..." +VERSION_INFO=$("$SCRIPTS_DIR/check-version.sh" --dotfiles "$DOTFILES_PATH") +echo "$VERSION_INFO" +echo "" + +# Parse version info +CURRENT=$(echo "$VERSION_INFO" | grep '^current=' | cut -d= -f2) +LATEST=$(echo "$VERSION_INFO" | grep '^latest=' | cut -d= -f2) +UPDATE_AVAILABLE=$(echo "$VERSION_INFO" | grep '^update_available=' | cut -d= -f2) + +if [[ "$UPDATE_AVAILABLE" != "yes" ]]; then + echo "✓ Already on latest version ($CURRENT)" + exit 0 +fi + +echo "Update available: $CURRENT → $LATEST" +echo "" + +# Step 2: Fetch SHA256 hash +echo "Step 2: Fetching SHA256 hash for version $LATEST..." +SHA256=$("$SCRIPTS_DIR/fetch-sha256.sh" "$LATEST") +echo "SHA256: $SHA256" +echo "" + +# Step 3a: Preview changes (dry-run) +echo "Step 3a: Previewing Nix file changes (dry-run)..." +"$SCRIPTS_DIR/update-nix-file.sh" "$LATEST" "$SHA256" --dotfiles "$DOTFILES_PATH" --dry-run +echo "" + +# Step 3b: Apply changes +echo "Step 3b: Applying Nix file updates..." +echo "⚠️ WARNING: This would modify $DOTFILES_PATH/pkgs/opencode/default.nix" +echo "⚠️ In a real workflow, ask user for confirmation before proceeding." +echo "" +read -p "Continue with file update? (y/N) " -n 1 -r +echo "" + +if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Aborted by user" + exit 1 +fi + +"$SCRIPTS_DIR/update-nix-file.sh" "$LATEST" "$SHA256" --dotfiles "$DOTFILES_PATH" +echo "" + +# Step 4: Rebuild system +echo "Step 4: Triggering system rebuild..." +echo "⚠️ WARNING: This will run 'sudo nixos-rebuild switch'" +echo "⚠️ This may take several minutes and will modify your system." +echo "" +read -p "Continue with system rebuild? (y/N) " -n 1 -r +echo "" + +if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Aborted by user" + echo "⚠️ Nix file has been updated but system not rebuilt." + echo " Run manually: cd $DOTFILES_PATH && sudo nixos-rebuild switch --flake .#" + exit 1 +fi + +cd "$DOTFILES_PATH" +HOSTNAME=$(hostname) +sudo nixos-rebuild switch --flake ".#$HOSTNAME" +echo "" + +# Step 5: Verify installation +echo "Step 5: Verifying installation..." +"$SCRIPTS_DIR/verify-update.sh" "$LATEST" +echo "" + +echo "=== Update Complete ===" +echo "OpenCode has been updated from $CURRENT to $LATEST" diff --git a/skills/update-opencode/references/nix-package-format.md b/skills/update-opencode/references/nix-package-format.md new file mode 100644 index 0000000..abdaa85 --- /dev/null +++ b/skills/update-opencode/references/nix-package-format.md @@ -0,0 +1,62 @@ +# OpenCode Nix Package Format Reference + +This file shows the structure of the OpenCode Nix package that scripts will modify. + +**Key fields to update:** +- Line 10: `version = "1.0.44";` - Update version number +- Line 14: `sha256 = "sha256-...";` - Update SRI hash + +**URL pattern (line 13):** +``` +https://github.com/sst/opencode/releases/download/v${finalAttrs.version}/opencode-linux-x64.zip +``` + +**Example package file:** + +```nix +{ lib +, stdenvNoCC +, fetchzip +, autoPatchelfHook +, stdenv +}: + +stdenvNoCC.mkDerivation (finalAttrs: { + pname = "opencode"; + version = "1.0.44"; # Pinned: works with opencode-skills@0.1.0 + + src = fetchzip { + url = "https://github.com/sst/opencode/releases/download/v${finalAttrs.version}/opencode-linux-x64.zip"; + sha256 = "sha256-q3K5w1lhzd7GhBesKbaD3jDo2B/twUDLmi8wrkWzWh4="; + stripRoot = false; + }; + + nativeBuildInputs = [ + autoPatchelfHook + ]; + + buildInputs = [ + stdenv.cc.cc.lib + ]; + + dontConfigure = true; + dontBuild = true; + dontStrip = true; + + installPhase = '' + runHook preInstall + install -Dm755 opencode $out/bin/opencode + runHook postInstall + ''; + + meta = with lib; { + description = "AI coding agent built for the terminal"; + homepage = "https://opencode.ai"; + license = licenses.mit; + maintainers = [ ]; + platforms = [ "x86_64-linux" ]; + mainProgram = "opencode"; + sourceProvenance = with sourceTypes; [ binaryNativeCode ]; + }; +}) +``` diff --git a/skills/update-opencode/scripts/check-version.sh b/skills/update-opencode/scripts/check-version.sh new file mode 100755 index 0000000..f2939c7 --- /dev/null +++ b/skills/update-opencode/scripts/check-version.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash +set -euo pipefail + +# check-version.sh - Compare current OpenCode version in Nix with latest GitHub release +# +# Usage: check-version.sh [--dotfiles PATH] +# +# Outputs: key=value pairs for parsing +# current=X.Y.Z +# latest=X.Y.Z +# update_available=yes|no + +# Default dotfiles path +DOTFILES_PATH="${HOME}/proj/dotfiles" + +# Parse arguments +while [[ $# -gt 0 ]]; do + case "$1" in + --dotfiles) + DOTFILES_PATH="$2" + shift 2 + ;; + *) + echo "Error: Unknown argument '$1'" >&2 + echo "Usage: $0 [--dotfiles PATH]" >&2 + exit 1 + ;; + esac +done + +# Check dependencies +for cmd in jq curl grep; do + if ! command -v "$cmd" >/dev/null 2>&1; then + echo "Error: Required command '$cmd' not found" >&2 + exit 1 + fi +done + +# Validate dotfiles path +NIX_FILE="${DOTFILES_PATH}/pkgs/opencode/default.nix" +if [[ ! -f "$NIX_FILE" ]]; then + echo "Error: Nix file not found at $NIX_FILE" >&2 + exit 1 +fi + +# Extract current version from Nix file +CURRENT_VERSION=$(grep -oP 'version\s*=\s*"\K[^"]+' "$NIX_FILE" || true) +if [[ -z "$CURRENT_VERSION" ]]; then + echo "Error: Could not parse version from $NIX_FILE" >&2 + exit 1 +fi + +# Query GitHub API for latest release +GITHUB_API="https://api.github.com/repos/sst/opencode/releases/latest" +LATEST_RELEASE=$(curl -s "$GITHUB_API" || true) +if [[ -z "$LATEST_RELEASE" ]]; then + echo "Error: Failed to query GitHub API at $GITHUB_API" >&2 + exit 1 +fi + +# Extract version from tag_name (format: "v1.0.44" -> "1.0.44") +LATEST_VERSION=$(echo "$LATEST_RELEASE" | jq -r '.tag_name' | sed 's/^v//') +if [[ -z "$LATEST_VERSION" || "$LATEST_VERSION" == "null" ]]; then + echo "Error: Could not parse latest version from GitHub API response" >&2 + exit 1 +fi + +# Compare versions using sort -V (version sort) +if [[ "$CURRENT_VERSION" == "$LATEST_VERSION" ]]; then + UPDATE_AVAILABLE="no" +else + # Check if latest is newer than current + NEWER=$(printf "%s\n%s" "$CURRENT_VERSION" "$LATEST_VERSION" | sort -V | tail -1) + if [[ "$NEWER" == "$LATEST_VERSION" ]]; then + UPDATE_AVAILABLE="yes" + else + UPDATE_AVAILABLE="no" + fi +fi + +# Output results +echo "current=$CURRENT_VERSION" +echo "latest=$LATEST_VERSION" +echo "update_available=$UPDATE_AVAILABLE" diff --git a/skills/update-opencode/scripts/fetch-sha256.sh b/skills/update-opencode/scripts/fetch-sha256.sh new file mode 100755 index 0000000..d693978 --- /dev/null +++ b/skills/update-opencode/scripts/fetch-sha256.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +set -euo pipefail + +# fetch-sha256.sh - Fetch and compute SRI hash for OpenCode release +# +# Usage: fetch-sha256.sh +# +# Example: fetch-sha256.sh 1.0.58 +# Output: sha256-ABC123... (SRI format) + +# Check arguments +if [[ $# -ne 1 ]]; then + echo "Error: Missing required argument" >&2 + echo "Usage: $0 " >&2 + echo "Example: $0 1.0.58" >&2 + exit 1 +fi + +VERSION="$1" + +# Validate version format (X.Y.Z) +if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then + echo "Error: Invalid version format '$VERSION' (expected X.Y.Z)" >&2 + exit 1 +fi + +# Check dependencies +for cmd in nix-prefetch-url nix; do + if ! command -v "$cmd" >/dev/null 2>&1; then + echo "Error: Required command '$cmd' not found" >&2 + exit 1 + fi +done + +# Construct download URL +URL="https://github.com/sst/opencode/releases/download/v${VERSION}/opencode-linux-x64.zip" + +# Fetch file and compute Nix base-32 hash +# Note: nix-prefetch-url outputs hash to stdout, potential warnings/info to stderr +# Allow nix-prefetch-url to fail gracefully so we can provide a custom error message +NIX_HASH=$(nix-prefetch-url --type sha256 "$URL" 2>/dev/null | head -1 || true) + +if [[ -z "$NIX_HASH" ]]; then + echo "Error: Failed to fetch or hash $URL" >&2 + echo " Verify version $VERSION exists on GitHub releases" >&2 + exit 1 +fi + +# Convert Nix base-32 hash to SRI format (sha256-...) +SRI_HASH=$(nix hash to-sri --type sha256 "$NIX_HASH" 2>/dev/null) + +if [[ -z "$SRI_HASH" ]]; then + echo "Error: Failed to convert hash to SRI format" >&2 + exit 1 +fi + +# Validate SRI hash format +if ! echo "$SRI_HASH" | grep -qE '^sha256-[A-Za-z0-9+/]+=*$'; then + echo "Error: Generated hash '$SRI_HASH' does not match SRI format" >&2 + exit 1 +fi + +# Output SRI hash +echo "$SRI_HASH" diff --git a/skills/update-opencode/scripts/update-nix-file.sh b/skills/update-opencode/scripts/update-nix-file.sh new file mode 100755 index 0000000..37bd7c0 --- /dev/null +++ b/skills/update-opencode/scripts/update-nix-file.sh @@ -0,0 +1,138 @@ +#!/usr/bin/env bash +set -euo pipefail + +# update-nix-file.sh - Atomically update OpenCode version and sha256 in Nix package +# +# Usage: update-nix-file.sh [--dotfiles PATH] [--dry-run] +# +# Example: update-nix-file.sh 1.0.58 sha256-ABC123... --dry-run +# +# Options: +# --dotfiles PATH Path to dotfiles repo (default: ~/proj/dotfiles) +# --dry-run Show changes without modifying file + +VERSION="" +SHA256="" +DOTFILES_PATH="${HOME}/proj/dotfiles" +DRY_RUN=false + +# Parse arguments +while [[ $# -gt 0 ]]; do + case "$1" in + --dotfiles) + DOTFILES_PATH="$2" + shift 2 + ;; + --dry-run) + DRY_RUN=true + shift + ;; + *) + if [[ -z "$VERSION" ]]; then + VERSION="$1" + shift + elif [[ -z "$SHA256" ]]; then + SHA256="$1" + shift + else + echo "Error: Unknown argument '$1'" >&2 + echo "Usage: $0 [--dotfiles PATH] [--dry-run]" >&2 + exit 1 + fi + ;; + esac +done + +# Validate required arguments +if [[ -z "$VERSION" || -z "$SHA256" ]]; then + echo "Error: Missing required arguments" >&2 + echo "Usage: $0 [--dotfiles PATH] [--dry-run]" >&2 + exit 1 +fi + +# Validate version format +if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then + echo "Error: Invalid version format '$VERSION' (expected X.Y.Z)" >&2 + exit 1 +fi + +# Validate SHA256 format (SRI) +if ! echo "$SHA256" | grep -qE '^sha256-[A-Za-z0-9+/]+=*$'; then + echo "Error: Invalid SHA256 format '$SHA256' (expected sha256-...)" >&2 + exit 1 +fi + +# Check dependencies +for cmd in sed grep; do + if ! command -v "$cmd" >/dev/null 2>&1; then + echo "Error: Required command '$cmd' not found" >&2 + exit 1 + fi +done + +# Validate Nix file exists +NIX_FILE="${DOTFILES_PATH}/pkgs/opencode/default.nix" +if [[ ! -f "$NIX_FILE" ]]; then + echo "Error: Nix file not found at $NIX_FILE" >&2 + exit 1 +fi + +# Validate that patterns exist in file (prevents no-op edits) +if ! grep -qE 'version\s*=\s*"[^"]*"' "$NIX_FILE"; then + echo "Error: Could not find 'version = \"...\"' pattern in $NIX_FILE" >&2 + exit 1 +fi + +if ! grep -qE 'sha256\s*=\s*"[^"]*"' "$NIX_FILE"; then + echo "Error: Could not find 'sha256 = \"...\"' pattern in $NIX_FILE" >&2 + exit 1 +fi + +# Dry-run: show diff without modifying +if [[ "$DRY_RUN" == true ]]; then + echo "=== Dry-run mode: showing changes ===" >&2 + echo "" >&2 + + # Show current values + CURRENT_VERSION=$(grep -oP 'version\s*=\s*"\K[^"]+' "$NIX_FILE") + CURRENT_SHA256=$(grep -oP 'sha256\s*=\s*"\K[^"]+' "$NIX_FILE") + + echo "Current version: $CURRENT_VERSION" >&2 + echo "New version: $VERSION" >&2 + echo "" >&2 + echo "Current sha256: $CURRENT_SHA256" >&2 + echo "New sha256: $SHA256" >&2 + echo "" >&2 + + # Show diff + echo "=== File diff ===" >&2 + sed -e "s/version = \"[^\"]*\"/version = \"$VERSION\"/" \ + -e "s/sha256 = \"[^\"]*\"/sha256 = \"$SHA256\"/" \ + "$NIX_FILE" | diff -u --color=auto "$NIX_FILE" - || true + + exit 0 +fi + +# Perform atomic update using sed +sed -i \ + -e "s/version = \"[^\"]*\"/version = \"$VERSION\"/" \ + -e "s/sha256 = \"[^\"]*\"/sha256 = \"$SHA256\"/" \ + "$NIX_FILE" + +# Verify update succeeded +UPDATED_VERSION=$(grep -oP 'version\s*=\s*"\K[^"]+' "$NIX_FILE") +UPDATED_SHA256=$(grep -oP 'sha256\s*=\s*"\K[^"]+' "$NIX_FILE") + +if [[ "$UPDATED_VERSION" != "$VERSION" ]]; then + echo "Error: Version update failed (expected $VERSION, got $UPDATED_VERSION)" >&2 + exit 1 +fi + +if [[ "$UPDATED_SHA256" != "$SHA256" ]]; then + echo "Error: SHA256 update failed (expected $SHA256, got $UPDATED_SHA256)" >&2 + exit 1 +fi + +echo "✓ Updated $NIX_FILE" +echo " version: $UPDATED_VERSION" +echo " sha256: $UPDATED_SHA256" diff --git a/skills/update-opencode/scripts/verify-update.sh b/skills/update-opencode/scripts/verify-update.sh new file mode 100755 index 0000000..a31db31 --- /dev/null +++ b/skills/update-opencode/scripts/verify-update.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +set -euo pipefail + +# verify-update.sh - Verify OpenCode installation after rebuild +# +# Usage: verify-update.sh +# +# Example: verify-update.sh 1.0.58 +# +# Exit codes: +# 0 - Verification succeeded +# 1 - Verification failed or error + +# Check arguments +if [[ $# -ne 1 ]]; then + echo "Error: Missing required argument" >&2 + echo "Usage: $0 " >&2 + echo "Example: $0 1.0.58" >&2 + exit 1 +fi + +EXPECTED_VERSION="$1" + +# Validate version format +if ! echo "$EXPECTED_VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then + echo "Error: Invalid version format '$EXPECTED_VERSION' (expected X.Y.Z)" >&2 + exit 1 +fi + +# Check dependencies +for cmd in timeout grep; do + if ! command -v "$cmd" >/dev/null 2>&1; then + echo "Error: Required command '$cmd' not found" >&2 + exit 1 + fi +done + +# Check if opencode is available +if ! command -v opencode >/dev/null 2>&1; then + echo "Error: opencode command not found in PATH" >&2 + echo " Ensure NixOS rebuild completed successfully" >&2 + exit 1 +fi + +# Query OpenCode version with timeout +# Output varies by version, extract first semver match +ACTUAL_VERSION=$(timeout 5 opencode --version 2>&1 | grep -oP '\d+\.\d+\.\d+' | head -1 || true) + +if [[ -z "$ACTUAL_VERSION" ]]; then + echo "Error: Could not determine OpenCode version" >&2 + echo " Command 'opencode --version' failed or returned unexpected format" >&2 + exit 1 +fi + +# Compare versions +if [[ "$ACTUAL_VERSION" == "$EXPECTED_VERSION" ]]; then + echo "✓ Verification succeeded" + echo " OpenCode version: $ACTUAL_VERSION" + exit 0 +else + echo "✗ Verification failed" >&2 + echo " Expected: $EXPECTED_VERSION" >&2 + echo " Actual: $ACTUAL_VERSION" >&2 + exit 1 +fi diff --git a/specs/002-update-opencode/checklists/requirements.md b/specs/002-update-opencode/checklists/requirements.md new file mode 100644 index 0000000..a8e332c --- /dev/null +++ b/specs/002-update-opencode/checklists/requirements.md @@ -0,0 +1,52 @@ +# Specification Quality Checklist: Update OpenCode Skill + +**Purpose**: Validate specification completeness and quality before proceeding to planning +**Created**: 2025-11-11 +**Feature**: [spec.md](../spec.md) + +## Content Quality + +- [x] No implementation details (languages, frameworks, APIs) +- [x] Focused on user value and business needs +- [x] Written for non-technical stakeholders +- [x] All mandatory sections completed + +## Requirement Completeness + +- [x] No [NEEDS CLARIFICATION] markers remain +- [x] Requirements are testable and unambiguous +- [x] Success criteria are measurable +- [x] Success criteria are technology-agnostic (no implementation details) +- [x] All acceptance scenarios are defined +- [x] Edge cases are identified +- [x] Scope is clearly bounded +- [x] Dependencies and assumptions identified + +## Feature Readiness + +- [x] All functional requirements have clear acceptance criteria +- [x] User scenarios cover primary flows +- [x] Feature meets measurable outcomes defined in Success Criteria +- [x] No implementation details leak into specification + +## Validation Notes + +**Content Quality**: ✅ PASS +- Specification describes WHAT and WHY without implementation details +- Success criteria focus on user outcomes (time to complete, success rates, audit compliance) +- Language is accessible to operations team members + +**Requirement Completeness**: ✅ PASS +- All 16 functional requirements are testable and specific +- No clarification markers present - all assumptions documented +- Edge cases cover network failures, partial updates, version conflicts +- Scope clearly separates in-scope (Nix updates) from out-of-scope (npm, rollback automation) + +**Feature Readiness**: ✅ PASS +- Three prioritized user stories (P1: check, P2: update, P3: pinning) with independent test scenarios +- Success criteria are measurable and technology-agnostic +- Acceptance scenarios use Given/When/Then format consistently + +**Overall Status**: ✅ READY FOR PLANNING + +All checklist items pass. The specification is complete, unambiguous, and ready for `/speckit.plan` to create implementation plan. diff --git a/specs/002-update-opencode/contracts/script-interface.md b/specs/002-update-opencode/contracts/script-interface.md new file mode 100644 index 0000000..e6f14b6 --- /dev/null +++ b/specs/002-update-opencode/contracts/script-interface.md @@ -0,0 +1,312 @@ +# Script Interface Contracts + +**Feature**: 002-update-opencode +**Date**: 2025-11-11 +**Purpose**: Define input/output contracts for bash helper scripts + +--- + +## Overview + +All scripts follow bash best practices: +- Shebang: `#!/usr/bin/env bash` +- Error handling: `set -euo pipefail` +- Exit codes: 0 = success, 1 = error +- Output: Results to stdout, errors to stderr +- Logging: Use `echo "message" >&2` for errors + +--- + +## 1. check-version.sh + +**Purpose**: Compare current OpenCode version against latest GitHub release. + +### Interface + +```bash +./check-version.sh [--dotfiles PATH] +``` + +**Arguments:** +- `--dotfiles PATH`: Optional. Path to dotfiles repo (default: ~/proj/dotfiles) + +**Exit Codes:** +- `0`: Success (version check completed) +- `1`: Error (file not found, API failure, parsing error) + +**Output (stdout):** +``` +current=1.0.44 +latest=1.0.58 +update_available=yes +``` + +Or if up-to-date: +``` +current=1.0.58 +latest=1.0.58 +update_available=no +``` + +**Output Format:** +- Key-value pairs, one per line +- `update_available` values: `yes` or `no` +- Can be sourced in bash: `eval "$(./check-version.sh)"` + +**Error Output (stderr):** +``` +Error: Nix package file not found: /path/to/default.nix +Error: GitHub API request failed (network error) +Error: Failed to parse version from Nix file +``` + +**Dependencies:** +- `curl` (GitHub API) +- `jq` (JSON parsing) +- `grep` (version extraction) + +--- + +## 2. fetch-sha256.sh + +**Purpose**: Fetch SHA256 hash for a specific OpenCode binary release using nix-prefetch-url. + +### Interface + +```bash +./fetch-sha256.sh VERSION +``` + +**Arguments:** +- `VERSION`: Required. Version to fetch (e.g., "1.0.58", no 'v' prefix) + +**Exit Codes:** +- `0`: Success (hash fetched and converted to SRI) +- `1`: Error (invalid version, network failure, nix-prefetch-url failed, conversion failed) + +**Output (stdout):** +``` +sha256-q3K5w1lhzd7GhBesKbaD3jDo2B/twUDLmi8wrkWzWh4= +``` + +**Output Format:** +- Single line containing SRI-formatted hash (sha256-...) +- Base64-encoded with possible trailing `=` padding +- No trailing newline issues (use `$(...)` safely) + +**Error Output (stderr):** +``` +Error: Version argument required +Error: Invalid version format: X.Y.Z +Error: nix-prefetch-url failed for version 1.0.58 +Error: GitHub binary release not found (404) +Error: Failed to convert hash to SRI format +``` + +**Dependencies:** +- `nix-prefetch-url` (fetching binary zip) +- `nix hash to-sri` (converting to SRI format) + +**Performance:** +- Typical: 2-5 seconds (download binary zip ~50MB + conversion) +- Cached: <0.5 seconds (Nix store hit + conversion) + +**Implementation Detail:** +```bash +# Downloads: https://github.com/sst/opencode/releases/download/v${VERSION}/opencode-linux-x64.zip +# Converts: Nix base-32 → SRI format (sha256-...) +``` + +--- + +## 3. update-nix-file.sh + +**Purpose**: Update version and sha256 fields in Nix package definition file. + +### Interface + +```bash +./update-nix-file.sh VERSION SHA256 [--dotfiles PATH] [--dry-run] +``` + +**Arguments:** +- `VERSION`: Required. New version string (e.g., "1.0.58") +- `SHA256`: Required. New sha256 hash in SRI format (sha256-...) +- `--dotfiles PATH`: Optional. Path to dotfiles repo (default: ~/proj/dotfiles) +- `--dry-run`: Optional. Show changes without writing + +**Exit Codes:** +- `0`: Success (file updated or dry-run completed) +- `1`: Error (file not found, not writable, pattern match failed) + +**Output (stdout) in dry-run mode:** +```diff +--- /home/user/proj/dotfiles/pkgs/opencode/default.nix ++++ /home/user/proj/dotfiles/pkgs/opencode/default.nix +@@ -2,7 +2,7 @@ +- version = "1.0.44"; ++ version = "1.0.58"; +- sha256 = "sha256-q3K5w1lhzd7GhBesKbaD3jDo2B/twUDLmi8wrkWzWh4="; ++ sha256 = "sha256-ABC123NewHashHere=="; +``` + +**Output (stdout) in normal mode:** +``` +Updated pkgs/opencode/default.nix: + version: 1.0.44 → 1.0.58 + sha256: sha256-q3K5...Wh4= → sha256-ABC1...e== +``` + +**Error Output (stderr):** +``` +Error: Nix package file not found: /path/to/default.nix +Error: File not writable: /path/to/default.nix +Error: Version pattern not found in file +Error: SHA256 pattern not found in file +Error: Invalid SHA256 format (must start with sha256-) +``` + +**Dependencies:** +- `sed` (file modification) +- `grep` (pattern validation) + +**Safety:** +- Validates patterns exist before modification +- Uses atomic sed -i (backup created automatically) +- Fails if either version or sha256 pattern not found exactly once + +--- + +## 4. verify-update.sh + +**Purpose**: Verify OpenCode was updated to expected version after rebuild. + +### Interface + +```bash +./verify-update.sh EXPECTED_VERSION [--timeout SECONDS] +``` + +**Arguments:** +- `EXPECTED_VERSION`: Required. Version that should be installed (e.g., "1.0.58") +- `--timeout SECONDS`: Optional. Max wait time for opencode command (default: 5) + +**Exit Codes:** +- `0`: Success (version matches) +- `1`: Error (command not found, version mismatch, timeout) + +**Output (stdout):** +``` +Verified: OpenCode version 1.0.58 installed successfully +``` + +Or on mismatch: +``` +Warning: Expected 1.0.58 but found 1.0.44 +Note: You may need to restart your shell or logout/login +``` + +**Error Output (stderr):** +``` +Error: opencode command not found in PATH +Error: opencode --version timed out after 5 seconds +Error: Could not parse version from opencode output +``` + +**Dependencies:** +- `opencode` (the CLI being updated) +- `timeout` command (for safety) + +**Behavior:** +- Runs `opencode --version` to check installed version +- Compares output against expected version +- Exit code 1 on mismatch but still outputs helpful message + +--- + +## Environment Variables + +All scripts may use: + +- `DOTFILES_PATH`: Override default dotfiles location (default: ~/proj/dotfiles) +- `DEBUG`: If set to "1", enable verbose logging to stderr +- `NO_COLOR`: If set, disable color output in error messages + +**Example:** +```bash +DOTFILES_PATH=/custom/path DEBUG=1 ./check-version.sh +``` + +--- + +## Error Handling Patterns + +All scripts follow these patterns: + +**Dependency checking:** +```bash +for cmd in jq curl grep; do + if ! command -v "$cmd" >/dev/null 2>&1; then + echo "Error: Required command not found: $cmd" >&2 + exit 1 + fi +done +``` + +**Input validation:** +```bash +if [[ $# -lt 1 ]]; then + echo "Error: Version argument required" >&2 + echo "Usage: $0 VERSION" >&2 + exit 1 +fi +``` + +**Network error handling:** +```bash +if ! response=$(curl -s --fail https://...); then + echo "Error: GitHub API request failed" >&2 + exit 1 +fi +``` + +**Pattern matching validation:** +```bash +if ! grep -q 'version = "[^"]*"' "$file"; then + echo "Error: Version pattern not found in $file" >&2 + exit 1 +fi +``` + +--- + +## Testing Contracts + +Each script should be testable independently: + +**Syntax validation:** +```bash +bash -n script.sh # Check for syntax errors +``` + +**Unit test pattern:** +```bash +# Test successful execution +result=$(./script.sh args) || { echo "FAIL: Expected success"; exit 1; } +echo "PASS: Script executed successfully" + +# Test error handling +./script.sh invalid-args 2>/dev/null && { echo "FAIL: Should have errored"; exit 1; } +echo "PASS: Error handled correctly" +``` + +**Integration test:** +```bash +# Full workflow test +version_info=$(./check-version.sh) || exit 1 +eval "$version_info" +if [[ "$update_available" == "yes" ]]; then + hash=$(./fetch-sha256.sh "$latest") || exit 1 + ./update-nix-file.sh "$latest" "$hash" --dry-run || exit 1 +fi +``` diff --git a/specs/002-update-opencode/plan.md b/specs/002-update-opencode/plan.md new file mode 100644 index 0000000..edc38fe --- /dev/null +++ b/specs/002-update-opencode/plan.md @@ -0,0 +1,165 @@ +# Implementation Plan: Update OpenCode Skill + +**Branch**: `002-update-opencode` | **Date**: 2025-11-11 | **Spec**: [spec.md](spec.md) +**Input**: Feature specification from `/specs/002-update-opencode/spec.md` + +**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/templates/commands/plan.md` for the execution workflow. + +## Summary + +Create an AI agent skill that automates checking for and applying OpenCode version updates in Nix-based dotfiles. The skill eliminates manual Nix file editing and SHA256 hash lookups by querying GitHub releases, fetching cryptographic hashes via nix-prefetch-url, updating package definitions, and triggering system rebuilds with user confirmation. + +## Technical Context + +**Language/Version**: Bash (no specific version requirement - standard POSIX + bash extensions) +**Primary Dependencies**: `jq`, `curl`, `nix-prefetch-url`, `sed`/`awk`, GitHub API +**Storage**: Filesystem - modifies `~/proj/dotfiles/pkgs/opencode/default.nix` +**Testing**: Manual verification scripts, bash syntax validation (`bash -n`) +**Target Platform**: NixOS or Nix home-manager on Linux +**Project Type**: AI Agent Skill (Markdown documentation + Bash helper scripts) +**Performance Goals**: Version checks <10 seconds (network-dependent), updates <5 minutes (rebuild time) +**Constraints**: Requires network access to GitHub API, sudo for system rebuild, dotfiles at known path +**Scale/Scope**: Personal/small team use, single user per execution, ~5 helper scripts + +## Constitution Check + +*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* + +**Skills Repository Constitution** (from `.specify/memory/constitution.md`): + +The constitution template is empty/placeholder. Based on project AGENTS.md and observed patterns: + +### I. Skill-First Design ✅ PASS +- Feature is designed as a standalone skill with clear scope +- SKILL.md will contain agent instructions +- Helper scripts are reusable and testable independently + +### II. Bash Best Practices ✅ PASS +- FR-012 mandates: shebang, `set -euo pipefail`, error handling +- Follows established patterns from niri-window-capture skill +- Scripts will use `jq --arg` for safe variable handling + +### III. Security Awareness ✅ PASS +- No security-sensitive operations (reading public GitHub data, modifying user's own files) +- No SECURITY.md required (unlike niri-window-capture which had invisible capture capability) +- User confirmation required before system modifications (FR-010) + +### IV. Fail-Safe Design ✅ PASS +- FR-009: No partial modifications - atomic operations only +- FR-008: Clear error messages, fail fast +- No sophisticated recovery - rely on git revert + +### V. Simplicity ✅ PASS +- Removed over-engineering (cross-validation, rate limit parsing, partial update detection) +- Focuses on core task: check, fetch, update, rebuild +- 12 focused requirements (down from 19 in initial draft) + +**Gate Result**: ✅ **PASS** - No violations, no complexity justification needed + +## Project Structure + +### Documentation (this feature) + +```text +specs/002-update-opencode/ +├── plan.md # This file +├── spec.md # Feature specification (completed) +├── research.md # Phase 0 output (to be created) +├── data-model.md # Phase 1 output (to be created) +├── quickstart.md # Phase 1 output (to be created) +├── contracts/ # Phase 1 output (to be created) +│ └── script-interface.md # Bash script contracts +├── checklists/ # Spec validation (completed) +│ └── requirements.md +└── tasks.md # Phase 2 output (/speckit.tasks - not created yet) +``` + +### Source Code (skills repository root) + +```text +skills/update-opencode/ +├── SKILL.md # Agent instructions (main skill definition) +├── README.md # User-facing documentation +├── scripts/ # Helper bash scripts +│ ├── check-version.sh # Compare current vs latest +│ ├── fetch-sha256.sh # Get SHA256 for version +│ ├── update-nix-file.sh # Modify default.nix safely +│ └── verify-update.sh # Confirm version after rebuild +├── examples/ # Example usage and outputs +│ ├── usage-example.sh +│ └── example-output.txt +└── references/ # Supporting documentation + └── nix-package-format.md # Example Nix package structure +``` + +**Structure Decision**: AI Agent Skill structure (documentation + bash scripts). This is not a traditional software project but a skill for Claude Code/OpenCode agents. The skill lives in `skills/update-opencode/` and will be deployed to dotfiles via `bin/deploy-skill.sh` script. Follows established pattern from worklog and niri-window-capture skills. + +## Complexity Tracking + +*Not applicable - Constitution Check passed with no violations.* + +--- + +## Phase 0: Research & Design Decisions + +**Status**: ✅ Complete + +**Research Tasks** (Completed): + +1. **Nix Package File Parsing**: Determine safest approach to read/update `version = "X.Y.Z"` and `sha256 = "hash"` fields + - Options: sed with precise patterns, awk field replacement, nix-instantiate parsing + - Need to preserve formatting, comments, surrounding code + +2. **GitHub API Usage**: Verify API endpoints and response structure for stable releases + - Confirm `/repos/sst/opencode/releases/latest` returns non-prerelease versions + - Document response fields: `tag_name`, `prerelease`, `tarball_url` + +3. **nix-prefetch-url Invocation**: Determine correct syntax for fetching SHA256 + - Command pattern for GitHub release tarballs + - Error handling for network failures + +4. **Version Comparison Logic**: Simple semantic version comparison in bash + - Parse major.minor.patch format + - Determine if update available (newer vs current) + +5. **System Rebuild Triggering**: Verify rebuild command and error handling + - Determine if `sudo nixos-rebuild switch` vs home-manager rebuild + - Capture rebuild output for error reporting + +**Output**: ✅ `research.md` completed with all decisions documented + +--- + +## Phase 1: Design & Contracts + +**Status**: ✅ Complete + +**Deliverables** (Completed): + +1. **data-model.md**: Entity definitions + - VersionInfo (current, latest, updateAvailable) + - NixPackageFile (path, version, sha256, content) + - GitHubRelease (tag, tarballUrl, isPrerelease) + +2. **contracts/script-interface.md**: Bash script contracts + - Input/output contracts for each helper script + - Exit codes, stdout format, stderr error messages + - Environment variables required + +3. **quickstart.md**: Quick reference for skill usage + - How agents invoke the skill + - Example agent conversations + - Manual script invocation for testing + +4. **Agent context update**: ✅ Completed + - Updated AGENTS.md with bash scripting context + - Added GitHub API and Nix tooling knowledge + - Documented dependencies: jq, curl, nix-prefetch-url, sed/awk + +--- + +## Phase 2: Task Breakdown + +**Status**: ⏳ Not started (separate command: `/speckit.tasks`) + +This phase is handled by the `/speckit.tasks` command after planning is complete. diff --git a/specs/002-update-opencode/quickstart.md b/specs/002-update-opencode/quickstart.md new file mode 100644 index 0000000..9fbe77d --- /dev/null +++ b/specs/002-update-opencode/quickstart.md @@ -0,0 +1,252 @@ +# Quick Start: Update OpenCode Skill + +**Feature**: 002-update-opencode +**Date**: 2025-11-11 +**For**: Developers and AI agents + +--- + +## For AI Agents (Claude Code / OpenCode) + +### When to Use This Skill + +The skill activates when users ask to: +- "Check for OpenCode updates" +- "Update OpenCode to the latest version" +- "Install OpenCode version X.Y.Z" +- "Is there a newer version of OpenCode available?" + +### Agent Workflow + +**P1: Check for Updates (Read-Only)** +``` +1. User: "Check for OpenCode updates" +2. Agent invokes: skills/update-opencode/scripts/check-version.sh +3. Agent reports: + - Current version: 1.0.44 + - Latest version: 1.0.58 + - Update available: Yes +``` + +**P2: Apply Update (Modifies System)** +``` +1. User: "Update OpenCode to latest" +2. Agent runs: ./check-version.sh to verify update needed +3. Agent asks: "Update will trigger system rebuild (~5 min). Continue? (yes/no)" +4. User confirms: "yes" +5. Agent executes: + a. ./fetch-sha256.sh 1.0.58 + b. ./update-nix-file.sh 1.0.58 + c. Trigger: sudo nixos-rebuild switch --flake ~/proj/dotfiles#delpad +6. Agent verifies: ./verify-update.sh 1.0.58 +7. Agent reports: "✓ OpenCode updated to 1.0.58" +``` + +**P3: Version Pinning** +``` +1. User: "Install OpenCode 1.0.44" +2. Agent follows same P2 workflow but with specific version +3. Agent warns if downgrading: "Current: 1.0.58 → Target: 1.0.44 (downgrade)" +``` + +### Error Handling for Agents + +**Network failures:** +``` +Agent: "Cannot reach GitHub API. Check network connection." +``` + +**Nix file not found:** +``` +Agent: "OpenCode package definition not found at ~/proj/dotfiles/pkgs/opencode/default.nix" +``` + +**SHA256 fetch fails:** +``` +Agent: "Failed to fetch SHA256 for version 1.0.58. Version may not exist." +``` + +**Rebuild fails:** +``` +Agent: "System rebuild failed. See error above. Revert with: cd ~/proj/dotfiles && git diff pkgs/opencode/default.nix" +``` + +--- + +## For Manual Testing + +### Check Current vs Latest Version + +```bash +cd skills/update-opencode/scripts +./check-version.sh +``` + +**Expected output:** +``` +current=1.0.44 +latest=1.0.58 +update_available=yes +``` + +### Fetch SHA256 for Specific Version + +```bash +./fetch-sha256.sh 1.0.58 +``` + +**Expected output:** +``` +0f3gmj8qpjz7xpay1r9ccavcvbsz10fczqvlmkj79sgq8mxs6v2z +``` + +(Takes 1-4 seconds) + +### Preview Nix File Changes (Dry Run) + +```bash +./update-nix-file.sh 1.0.58 0f3gmj8qpjz7xpay1r9ccavcvbsz10fczqvlmkj79sgq8mxs6v2z --dry-run +``` + +**Expected output:** +```diff +--- /home/user/proj/dotfiles/pkgs/opencode/default.nix ++++ /home/user/proj/dotfiles/pkgs/opencode/default.nix +@@ -2,7 +2,7 @@ +- version = "1.0.44"; ++ version = "1.0.58"; +- sha256 = "oldhasholdhasholdhasholdhashol"; ++ sha256 = "0f3gmj8qpjz7xpay1r9ccavcvbsz10fczqvlmkj79sgq8mxs6v2z"; +``` + +### Apply Update (Actual Modification) + +```bash +# Update Nix file +./update-nix-file.sh 1.0.58 0f3gmj8qpjz7xpay1r9ccavcvbsz10fczqvlmkj79sgq8mxs6v2z + +# Trigger rebuild +cd ~/proj/dotfiles +sudo nixos-rebuild switch --flake .#delpad + +# Verify +cd ~/proj/skills/skills/update-opencode/scripts +./verify-update.sh 1.0.58 +``` + +### Full Workflow Test + +```bash +#!/usr/bin/env bash +# Test complete update workflow (safe - uses dry-run) + +set -euo pipefail + +cd "$(dirname "$0")" + +echo "==> Checking for updates..." +version_info=$(./check-version.sh) +eval "$version_info" + +echo "Current: $current" +echo "Latest: $latest" +echo "Update available: $update_available" + +if [[ "$update_available" == "yes" ]]; then + echo "" + echo "==> Fetching SHA256 for version $latest..." + hash=$(./fetch-sha256.sh "$latest") + echo "Hash: $hash" + + echo "" + echo "==> Previewing Nix file changes (dry-run)..." + ./update-nix-file.sh "$latest" "$hash" --dry-run + + echo "" + echo "✓ Test complete (no actual changes made)" +else + echo "" + echo "✓ Already on latest version" +fi +``` + +--- + +## Prerequisites + +**System requirements:** +- NixOS or Nix home-manager +- Network access to api.github.com +- Sudo access for system rebuild + +**Tool dependencies:** +```bash +# Check if required tools are available +for cmd in curl jq nix-prefetch-url grep sed; do + command -v "$cmd" >/dev/null 2>&1 || echo "Missing: $cmd" +done +``` + +**Dotfiles structure:** +``` +~/proj/dotfiles/ +└── pkgs/ + └── opencode/ + └── default.nix # Must contain version = "..." and sha256 = "..." +``` + +--- + +## Common Issues + +### "opencode command not found" after rebuild + +**Cause**: Shell hasn't reloaded environment +**Fix**: Restart shell or run: `hash -r` + +### "Network error" when checking versions + +**Cause**: GitHub API unreachable or rate limited +**Fix**: Check network, wait if rate limited (60 req/hour for unauthenticated) + +### "Version pattern not found in file" + +**Cause**: Nix file has unexpected format +**Fix**: Verify file contains exactly: `version = "X.Y.Z";` and `sha256 = "hash";` + +### "nix-prefetch-url failed" + +**Cause**: Version doesn't exist on GitHub or network issue +**Fix**: Verify version exists: `curl -s https://api.github.com/repos/sst/opencode/releases | jq '.[].tag_name'` + +--- + +## Skill File Structure + +``` +skills/update-opencode/ +├── SKILL.md # Agent reads this for instructions +├── README.md # Humans read this for documentation +├── scripts/ +│ ├── check-version.sh # Compare current vs latest +│ ├── fetch-sha256.sh # Get hash for version +│ ├── update-nix-file.sh # Modify default.nix +│ └── verify-update.sh # Confirm version after rebuild +├── examples/ +│ ├── usage-example.sh # Complete workflow example +│ └── example-output.txt # Sample script outputs +└── references/ + └── nix-package-format.md # Example Nix package structure +``` + +--- + +## Next Steps + +After skill implementation: + +1. **Test each script independently** with sample data +2. **Test full workflow** with actual OpenCode versions +3. **Deploy to dotfiles** using `./bin/deploy-skill.sh update-opencode` +4. **Configure Nix** to install skill to `~/.claude/skills/` and `~/.config/opencode/skills/` +5. **Test with agent** by asking "check for OpenCode updates" diff --git a/specs/002-update-opencode/research.md b/specs/002-update-opencode/research.md new file mode 100644 index 0000000..c8dffce --- /dev/null +++ b/specs/002-update-opencode/research.md @@ -0,0 +1,76 @@ +# Implementation Decisions: Update OpenCode Skill + +**Date**: 2025-11-11 +**Feature**: 002-update-opencode + +--- + +## Key Technical Decisions + +### 1. Nix File Parsing +**Decision**: Use `sed` with regex patterns +**Rationale**: Simple, preserves formatting, no complex parsing needed +**Commands**: +```bash +# Read: grep -oP 'version\s*=\s*"\K[^"]+' file +# Update: sed -i 's/version = "[^"]*"/version = "X.Y.Z"/' file +``` + +### 2. GitHub API +**Decision**: Use `/repos/sst/opencode/releases/latest` endpoint +**Rationale**: Automatically excludes pre-releases, single query +**Rate Limits**: 60/hour unauthenticated, 5000/hour authenticated + +### 3. SHA256 Hash Format +**Decision**: Use SRI format (`sha256-...`), not Nix base-32 +**Rationale**: OpenCode package uses `fetchzip` which expects SRI format +**Implementation**: +```bash +# Fetch: nix-prefetch-url --type sha256 +# Convert: nix hash to-sri --type sha256 +# Output: sha256-ABC123... +``` +**Critical**: Binary releases (~50MB zips), NOT source tarballs + +### 4. Version Comparison +**Decision**: Use `sort -V` (version sort) +**Rationale**: Handles semantic versioning correctly, standard tool + +### 5. System Rebuild +**Decision**: Detect `nixos-rebuild` vs `home-manager` +**Commands**: +```bash +if command -v nixos-rebuild >/dev/null 2>&1; then + sudo nixos-rebuild switch --flake ~/proj/dotfiles#delpad +elif command -v home-manager >/dev/null 2>&1; then + home-manager switch --flake ~/proj/dotfiles +fi +``` + +--- + +## Important Details + +**Binary Release URL Pattern**: +``` +https://github.com/sst/opencode/releases/download/v${VERSION}/opencode-linux-x64.zip +``` + +**Current Package Format** (from ~/proj/dotfiles/pkgs/opencode/default.nix): +```nix +{ + version = "1.0.44"; + sha256 = "sha256-q3K5w1lhzd7GhBesKbaD3jDo2B/twUDLmi8wrkWzWh4="; + # Uses fetchzip for binary releases +} +``` + +**Error Handling**: Fail fast with clear messages, no sophisticated retry logic + +--- + +## References + +- GitHub API docs: https://docs.github.com/en/rest/releases +- nix-prefetch-url: `man nix-prefetch-url` +- SRI format: https://www.w3.org/TR/SRI/ diff --git a/specs/002-update-opencode/spec.md b/specs/002-update-opencode/spec.md new file mode 100644 index 0000000..64781fe --- /dev/null +++ b/specs/002-update-opencode/spec.md @@ -0,0 +1,166 @@ +# Feature Specification: Update OpenCode Skill + +**Feature Branch**: `002-update-opencode` +**Created**: 2025-11-11 +**Status**: Draft +**Input**: User description: "Implement update-opencode skill for checking and applying OpenCode version updates in Nix-based dotfiles setup" + +## User Scenarios & Testing *(mandatory)* + +### User Story 1 - Check for OpenCode Updates (Priority: P1) + +An operations team member or developer wants to know if a newer version of OpenCode is available without manually checking GitHub releases or editing configuration files. + +**Why this priority**: This is the minimum viable functionality - being able to query version information. It delivers immediate value by automating the manual check process and provides awareness of available updates without any system changes. + +**Independent Test**: Can be fully tested by invoking the skill with "check for OpenCode updates" and verifying it returns current version, latest available version, and whether an update is available. No system modifications required. + +**Acceptance Scenarios**: + +1. **Given** OpenCode is installed via Nix at version 1.0.44, **When** user asks agent to check OpenCode version, **Then** agent reports current version (1.0.44), latest GitHub release version, and whether update is available +2. **Given** user is already on latest version, **When** checking for updates, **Then** agent confirms system is up-to-date with no action needed +3. **Given** GitHub API is unreachable, **When** checking for updates, **Then** agent reports network error with clear message and does not fail silently + +--- + +### User Story 2 - Apply OpenCode Update (Priority: P2) + +A user discovers an update is available and wants to apply it by having the agent automatically update the Nix package definition, fetch the correct SHA256 hash, and trigger a system rebuild. + +**Why this priority**: This is the core automation value - eliminating manual Nix file editing and SHA256 fetching. Depends on P1 (checking versions) but delivers the main time-saving benefit. + +**Independent Test**: Can be tested by requesting "update OpenCode to version X" with the agent performing file updates, rebuild trigger, and verification. Requires P1 to detect version mismatch first. + +**Acceptance Scenarios**: + +1. **Given** newer version exists (e.g., 1.0.51), **When** user confirms update after being presented with rebuild impact summary, **Then** agent fetches SHA256 for new version, updates `pkgs/opencode/default.nix` with new version and hash, triggers system rebuild, and reports success +2. **Given** update is requested but already on latest, **When** update command issued, **Then** agent confirms no update needed and does not trigger rebuild +3. **Given** SHA256 fetch fails for new release, **When** attempting update, **Then** agent reports error clearly and does not modify Nix files or trigger rebuild +4. **Given** Nix file update succeeds but rebuild fails, **When** update attempted, **Then** agent reports rebuild failure with error details and suggests rollback or manual intervention + +--- + +### User Story 3 - Update to Specific Version (Priority: P3) + +A user needs to update (or downgrade) to a specific OpenCode version for compatibility reasons, such as maintaining compatibility with a specific plugin version. + +**Why this priority**: This is an advanced use case for version pinning. While valuable for production stability, most users will use P2 (latest version). Can be implemented after core functionality works. + +**Independent Test**: Can be tested independently by requesting "update OpenCode to version 1.0.44" and verifying the agent installs that specific version regardless of what's latest. + +**Acceptance Scenarios**: + +1. **Given** user specifies target version (e.g., "update to 1.0.44"), **When** agent processes request, **Then** agent fetches that specific version's SHA256, updates Nix file to target version, rebuilds, and verifies +2. **Given** specified version doesn't exist on GitHub, **When** update requested, **Then** agent reports version not found and lists available versions +3. **Given** downgrading from 1.0.51 to 1.0.44, **When** update executed, **Then** agent warns about downgrade, proceeds if confirmed, and successfully installs older version + +--- + +### Edge Cases + +- What if the Nix package file (`default.nix`) has unexpected structure or is missing? (Report error, don't attempt to parse) +- What if GitHub API fails? (Report error, suggest manual check) +- What if nix-prefetch-url fails? (Report error, don't modify files) + +## Requirements *(mandatory)* + +### Functional Requirements + +**Core Functionality:** +- **FR-001**: Skill MUST read current OpenCode version from `pkgs/opencode/default.nix` in dotfiles repository +- **FR-002**: Skill MUST query GitHub API for latest stable release (excluding pre-releases) +- **FR-003**: Skill MUST compare versions and report status (up-to-date, update available, or error) +- **FR-004**: Skill MUST fetch SHA256 hash using `nix-prefetch-url` for target version +- **FR-005**: Skill MUST update version and SHA256 in `pkgs/opencode/default.nix` preserving file structure +- **FR-006**: Skill MUST trigger system rebuild after updating package definition +- **FR-007**: Skill MUST verify successful update by running `opencode --version` + +**Error Handling:** +- **FR-008**: Skill MUST report clear errors for network failures, missing files, or invalid versions +- **FR-009**: Skill MUST not modify files if any prerequisite step fails + +**User Interaction:** +- **FR-010**: Skill MUST confirm with user before triggering system rebuild +- **FR-011**: Skill MUST support updating to specific version for pinning + +**Quality:** +- **FR-012**: Helper scripts MUST follow bash best practices (shebang, `set -euo pipefail`, error handling) + +### Key Entities + +- **OpenCode Version**: Semantic version string (e.g., "1.0.44") representing installed or available OpenCode CLI version +- **Nix Package Definition**: File at `pkgs/opencode/default.nix` containing version, SHA256 hash, and package metadata +- **GitHub Release**: Release artifact on `sst/opencode` repository with version tag, release assets, and metadata +- **SHA256 Hash**: Cryptographic hash of OpenCode release tarball required for Nix package integrity verification + +## Success Criteria *(mandatory)* + +### Measurable Outcomes + +- **SC-001**: Users can check for updates and apply them without manual Nix file editing or SHA256 lookups +- **SC-002**: Update process fails safely - no partial modifications if any step errors +- **SC-003**: Skill correctly identifies current version, latest version, and whether update is available +- **SC-004**: Skill supports version pinning for compatibility (install specific version) + +## Clarifications + +### Session 2025-11-11 + +- Q: How should the agent handle rebuild confirmation? → A: Ask user to confirm before triggering rebuild +- Removed over-engineering: Cross-validation, rate limit parsing, partial update detection - these add complexity without clear value for personal/small-team use + +## Assumptions + +- Dotfiles repository is located at `~/proj/dotfiles` with predictable structure +- OpenCode package definition is at `pkgs/opencode/default.nix` within dotfiles +- System uses NixOS or Nix home-manager with rebuild command available +- User has network access to GitHub API (api.github.com) +- User has permissions to modify dotfiles and trigger system rebuild +- Nix package definition follows standard format with `version = "X.Y.Z"` and `sha256 = "hash"` fields +- GitHub releases follow semantic versioning (major.minor.patch) +- User wants stable releases by default (not alpha/beta/rc versions) +- System rebuild command is `sudo nixos-rebuild switch --flake .#delpad` or user can specify alternative +- Verification can be done by running `opencode --version` after rebuild + +## Scope + +### In Scope + +- Check current version from Nix file +- Query GitHub for latest stable release +- Compare versions and report status +- Fetch SHA256 hash with nix-prefetch-url +- Update Nix file (version + hash) +- Trigger system rebuild +- Verify update with `opencode --version` +- Support specific version pinning +- Basic error handling (fail fast, clear messages) + +### Out of Scope + +- Non-Nix installations (npm, binary) +- Automatic/scheduled checks +- Automatic rollback (use git revert) +- Plugin compatibility checking +- GUI/web interface +- Multiple dotfiles repos +- Cross-platform (Nix-only) +- Sophisticated error recovery +- Rate limit handling beyond "fail with error" +- Audit logging (not needed for personal use) +- Cross-validation of multiple sources + +## Dependencies + +- **External**: GitHub API (api.github.com) +- **Tools**: `nix-prefetch-url`, `jq`, `curl`, `sed`/`awk` +- **Environment**: NixOS or home-manager with rebuild command +- **Permissions**: Sudo for rebuild + +## Constraints + +- Requires network access to GitHub +- Rebuild takes several minutes +- Assumes dotfiles at `~/proj/dotfiles` +- Assumes standard Nix package file structure +- OpenCode needs restart after rebuild diff --git a/specs/002-update-opencode/tasks.md b/specs/002-update-opencode/tasks.md new file mode 100644 index 0000000..08c07d3 --- /dev/null +++ b/specs/002-update-opencode/tasks.md @@ -0,0 +1,171 @@ +# Tasks: Update OpenCode Skill + +**Simplified**: Focused on essential implementation work, removed over-engineering +**Prerequisites**: spec.md (requirements), contracts/script-interface.md (script I/O) + +--- + +## Implementation Tasks + +### Setup (2 tasks) + +- [ ] T001 Create skill directory structure: `mkdir -p skills/update-opencode/{scripts,examples,references}` +- [ ] T002 Copy ~/proj/dotfiles/pkgs/opencode/default.nix to skills/update-opencode/references/nix-package-format.md as reference example + +### Core Scripts (4 tasks - can run in parallel) + +- [ ] T003 [P] Implement skills/update-opencode/scripts/check-version.sh (reads Nix file, queries GitHub API, compares versions, outputs key=value format) +- [ ] T004 [P] Implement skills/update-opencode/scripts/fetch-sha256.sh (runs nix-prefetch-url, converts to SRI format via `nix hash to-sri`) +- [ ] T005 [P] Implement skills/update-opencode/scripts/update-nix-file.sh (uses sed to update version and sha256 fields, supports --dry-run) +- [ ] T006 [P] Implement skills/update-opencode/scripts/verify-update.sh (runs `opencode --version`, compares to expected) + +### Documentation (3 tasks) + +- [ ] T007 Create skills/update-opencode/SKILL.md with YAML frontmatter (name, description), When to Use section, and complete workflow instructions +- [ ] T008 [P] Create skills/update-opencode/README.md with installation, prerequisites (jq, curl, nix-prefetch-url, sed), and usage examples +- [ ] T009 [P] Create skills/update-opencode/examples/usage-example.sh demonstrating complete check → fetch → update → verify workflow + +### Testing & Deployment (1 task) + +- [ ] T010 Test complete workflow: run check-version.sh, fetch-sha256.sh with test version, update-nix-file.sh in dry-run mode, verify all error handling works, then deploy to dotfiles + +--- + +## Task Details + +### T001: Create Directory Structure +```bash +mkdir -p skills/update-opencode/{scripts,examples,references} +``` + +### T003: check-version.sh +**Key logic:** +- Dependency check: jq, curl, grep +- Parse current version: `grep -oP 'version\s*=\s*"\K[^"]+' default.nix` +- Query GitHub: `curl -s https://api.github.com/repos/sst/opencode/releases/latest | jq -r '.tag_name'` +- Compare with `sort -V` +- Output: `current=X.Y.Z\nlatest=X.Y.Z\nupdate_available=yes/no` + +### T004: fetch-sha256.sh +**Key logic:** +```bash +VERSION="$1" +URL="https://github.com/sst/opencode/releases/download/v${VERSION}/opencode-linux-x64.zip" +NIX_HASH=$(nix-prefetch-url --type sha256 "$URL" 2>/dev/null | head -1) +nix hash to-sri --type sha256 "$NIX_HASH" +``` + +### T005: update-nix-file.sh +**Key logic:** +```bash +sed -i \ + -e "s/version = \"[^\"]*\"/version = \"$VERSION\"/" \ + -e "s/sha256 = \"[^\"]*\"/sha256 = \"$SHA256\"/" \ + pkgs/opencode/default.nix +``` +Add --dry-run support to show diff without modifying. + +### T006: verify-update.sh +**Key logic:** +```bash +EXPECTED="$1" +ACTUAL=$(timeout 5 opencode --version 2>&1 | grep -oP '\d+\.\d+\.\d+' | head -1) +[[ "$ACTUAL" == "$EXPECTED" ]] && echo "✓ Verified" || echo "✗ Mismatch" +``` + +### T007: SKILL.md +**Structure:** +```yaml +--- +name: update-opencode +description: Check for and apply OpenCode version updates in Nix-based dotfiles. Use when asked to update OpenCode, check OpenCode version, or upgrade OpenCode. +--- + +# Update OpenCode Skill + +## When to Use +- User asks to check OpenCode version +- User asks to update/upgrade OpenCode +- User wants to install specific OpenCode version + +## Process +1. Check current vs latest: Run scripts/check-version.sh +2. If update available and user confirms: + a. Fetch SHA256: scripts/fetch-sha256.sh + b. Update Nix file: scripts/update-nix-file.sh + c. Trigger rebuild: cd ~/proj/dotfiles && sudo nixos-rebuild switch --flake .#delpad + d. Verify: scripts/verify-update.sh + +## Requirements +- Tools: jq, curl, nix-prefetch-url, sed +- Permissions: sudo for rebuild +- Network: GitHub API access +``` + +### T010: Testing Workflow +```bash +cd skills/update-opencode/scripts + +# Test syntax +bash -n *.sh + +# Test version check +./check-version.sh + +# Test hash fetch (use known version) +./fetch-sha256.sh 1.0.44 + +# Test update in dry-run +./update-nix-file.sh 1.0.58 sha256-ABC123... --dry-run + +# Test error handling +./check-version.sh --dotfiles /nonexistent # Should error gracefully +./fetch-sha256.sh 99.99.99 # Should handle 404 + +# If all pass, deploy +cd ~/proj/skills +./bin/deploy-skill.sh update-opencode +``` + +--- + +## Execution Notes + +**Estimated Time**: 3-4 hours total +- Setup: 10 min +- Scripts: 2 hours (4 scripts × 30 min each) +- Documentation: 1 hour +- Testing/Deploy: 30 min + +**All scripts follow:** +- Shebang: `#!/usr/bin/env bash` +- Error handling: `set -euo pipefail` +- Safe jq usage: `jq --arg` for variable interpolation +- Clear errors to stderr: `echo "Error: ..." >&2; exit 1` + +**Key Implementation Notes:** +- Use SRI hash format (sha256-...), not Nix base-32 +- Binary releases (~50MB), not source tarballs +- URL pattern: `https://github.com/sst/opencode/releases/download/v${VERSION}/opencode-linux-x64.zip` +- Atomic updates: validate patterns exist before sed -i + +--- + +## Deliverables + +``` +skills/update-opencode/ +├── SKILL.md # Agent instructions (~50 lines) +├── README.md # User documentation (~50 lines) +├── scripts/ +│ ├── check-version.sh # ~50 lines +│ ├── fetch-sha256.sh # ~40 lines +│ ├── update-nix-file.sh # ~60 lines +│ └── verify-update.sh # ~30 lines +├── examples/ +│ └── usage-example.sh # ~30 lines +└── references/ + └── nix-package-format.md # Example Nix file +``` + +**Total**: ~310 lines of actual code/docs (down from 1,106 lines of planning docs)