skills/specs/002-update-opencode/contracts/script-interface.md
dan 99187460b1 feat(update-opencode): add skill for automating OpenCode version updates in Nix
Implement complete workflow for checking and applying OpenCode updates:
- check-version.sh: Compare current vs latest GitHub release
- fetch-sha256.sh: Fetch SHA256 hash using nix-prefetch-url
- update-nix-file.sh: Update Nix package definition with dry-run support
- verify-update.sh: Verify installed version matches expectation

Includes comprehensive documentation (SKILL.md for agents, README.md for users)
and full spec in specs/002-update-opencode/ with user stories and acceptance criteria.

Eliminates manual Nix file editing and hash lookups for OpenCode updates.
2025-11-15 13:35:58 -08:00

7.2 KiB

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

./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

./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:

# 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

./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:

--- /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

./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:

DOTFILES_PATH=/custom/path DEBUG=1 ./check-version.sh

Error Handling Patterns

All scripts follow these patterns:

Dependency checking:

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:

if [[ $# -lt 1 ]]; then
  echo "Error: Version argument required" >&2
  echo "Usage: $0 VERSION" >&2
  exit 1
fi

Network error handling:

if ! response=$(curl -s --fail https://...); then
  echo "Error: GitHub API request failed" >&2
  exit 1
fi

Pattern matching validation:

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 -n script.sh  # Check for syntax errors

Unit test pattern:

# 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:

# 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