diff --git a/.agent/AGENTS.md b/.agent/AGENTS.md index f511ab47..2ebddd71 100644 --- a/.agent/AGENTS.md +++ b/.agent/AGENTS.md @@ -407,14 +407,20 @@ Subagents provide specialized capabilities. Read them when tasks require domain ## Getting Started -Run `setup.sh` from the aidevops repository to install agents locally: +**Installation:** ```bash -cd ~/Git/aidevops -./setup.sh +# npm (recommended) +npm install -g aidevops + +# Homebrew +brew install marcusquinn/tap/aidevops + +# curl (manual) +bash <(curl -fsSL https://aidevops.sh) ``` -This copies agents to `~/.aidevops/agents/` and configures AI assistants. +This installs the CLI and deploys agents to `~/.aidevops/agents/`. **Initialize aidevops in any project:** @@ -434,9 +440,18 @@ aidevops features # List available features | `aidevops upgrade-planning` | Upgrade TODO.md/PLANS.md to latest templates | | `aidevops features` | List available features | | `aidevops status` | Check installation status | -| `aidevops update` | Update to latest version | +| `aidevops update` | Update framework + check registered projects | +| `aidevops upgrade` | Alias for update | +| `aidevops repos` | List registered projects | +| `aidevops repos add` | Register current project | +| `aidevops detect` | Find unregistered aidevops projects | +| `aidevops update-tools` | Check for outdated tools | | `aidevops uninstall` | Remove aidevops | +**Project tracking:** When you run `aidevops init`, the project is registered in `~/.config/aidevops/repos.json`. Running `aidevops update` will check all registered projects and offer to update their `.aidevops.json` version. + +**Auto-detection:** When you clone a repo that has `.aidevops.json`, the CLI will suggest registering it. Run `aidevops detect` to scan `~/Git/` for unregistered projects. + For AI-assisted setup guidance, see `aidevops/setup.md`. ## Progressive Disclosure diff --git a/.agent/scripts/full-loop-helper.sh b/.agent/scripts/full-loop-helper.sh index b2dcec68..a231a73c 100755 --- a/.agent/scripts/full-loop-helper.sh +++ b/.agent/scripts/full-loop-helper.sh @@ -46,6 +46,7 @@ readonly STATE_FILE="${STATE_DIR}/full-loop.local.md" # Legacy state directory (for backward compatibility during migration) # shellcheck disable=SC2034 # Defined for documentation, used in cancel checks readonly LEGACY_STATE_DIR=".claude" +# shellcheck disable=SC2034 # Defined for backward compatibility path reference readonly LEGACY_STATE_FILE="${LEGACY_STATE_DIR}/full-loop.local.md" # Default settings diff --git a/.agent/scripts/version-manager.sh b/.agent/scripts/version-manager.sh index 7b963c8c..0db4c789 100755 --- a/.agent/scripts/version-manager.sh +++ b/.agent/scripts/version-manager.sh @@ -595,60 +595,56 @@ generate_release_notes() { IFS='.' read -r major minor patch <<< "$version" cat << EOF -🚀 **AI DevOps Framework v$version** +## AI DevOps Framework v$version -## 📋 **What's New in v$version** +### Installation -### ✨ **Key Features** -- Enhanced framework capabilities and integrations -- Improved documentation and user experience -- Quality improvements and bug fixes -- Updated service integrations and configurations +\`\`\`bash +# npm (recommended) +npm install -g aidevops -### 🔧 **Technical Improvements** -- Framework optimization and performance enhancements -- Security updates and best practices implementation -- Documentation updates and clarity improvements -- Configuration and setup enhancements +# Homebrew +brew install marcusquinn/tap/aidevops -### 📊 **Framework Status** -- **27+ Service Integrations**: Complete DevOps ecosystem coverage -- **Enterprise Security**: Zero credential exposure patterns -- **Quality Monitoring**: A+ grades across all platforms -- **Professional Versioning**: Semantic version management -- **Comprehensive Documentation**: 18,000+ lines of guides +# curl +bash <(curl -fsSL https://aidevops.sh) +\`\`\` + +### What's New + +See [CHANGELOG.md](CHANGELOG.md) for detailed changes. + +### Quick Start -## 🚀 **Quick Start** +\`\`\`bash +# Check installation +aidevops status -`bash` -# Clone the repository -git clone https://github.com/marcusquinn/aidevops.git -cd aidevops +# Initialize in a project +aidevops init -# Run setup wizard -bash setup.sh +# Update framework + projects +aidevops update -# Configure your services -# Follow the comprehensive documentation in .agent/ +# List registered projects +aidevops repos \`\`\` -## 📚 **Documentation** +### Documentation + - **[Setup Guide](README.md)**: Complete framework setup -- **[API Integrations](.agent/API-INTEGRATIONS.md)**: 27+ service APIs -- **[Security Guide](.agent/SECURITY.md)**: Enterprise security practices -- **[MCP Integration](.agent/MCP-INTEGRATIONS.md)**: Real-time AI data access +- **[User Guide](.agent/AGENTS.md)**: AI assistant integration +- **[API Integrations](.agent/aidevops/api-integrations.md)**: Service APIs + +### Links -## 🔗 **Links** +- **Website**: https://aidevops.sh - **Repository**: https://github.com/marcusquinn/aidevops -- **Documentation**: Available in repository - **Issues**: https://github.com/marcusquinn/aidevops/issues -- **Discussions**: https://github.com/marcusquinn/aidevops/discussions --- **Full Changelog**: https://github.com/marcusquinn/aidevops/compare/v1.0.0...v$version - -**Copyright © Marcus Quinn 2025** - All rights reserved under MIT License EOF return 0 } diff --git a/.codacy.yml b/.codacy.yml index 1597fde9..cede2786 100644 --- a/.codacy.yml +++ b/.codacy.yml @@ -10,6 +10,12 @@ engines: - 'configs/**' - 'tests/**' + # Security analysis + semgrep: + enabled: true + exclude_paths: + - 'scripts/**' # npm postinstall paths are safe (package-relative) + # Documentation analysis markdownlint: enabled: true @@ -28,6 +34,7 @@ engines: exclude_paths: - 'configs/**' - 'tests/**' + - 'scripts/**' # npm postinstall uses CommonJS for Node.js compatibility exclude_paths: # Exclude generated or third-party files diff --git a/.github/workflows/publish-packages.yml b/.github/workflows/publish-packages.yml new file mode 100644 index 00000000..7c5285cc --- /dev/null +++ b/.github/workflows/publish-packages.yml @@ -0,0 +1,137 @@ +name: Publish Packages + +on: + release: + types: [published] + workflow_dispatch: + inputs: + version: + description: 'Version to publish (e.g., 2.52.1)' + required: true + +jobs: + publish-npm: + name: Publish to npm + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + registry-url: 'https://registry.npmjs.org' + + - name: Get version + id: version + env: + INPUT_VERSION: ${{ github.event.inputs.version }} + run: | + if [ -n "$INPUT_VERSION" ]; then + echo "version=$INPUT_VERSION" >> $GITHUB_OUTPUT + else + echo "version=$(cat VERSION)" >> $GITHUB_OUTPUT + fi + + - name: Update package.json version + env: + PKG_VERSION: ${{ steps.version.outputs.version }} + run: | + npm version "$PKG_VERSION" --no-git-tag-version --allow-same-version + + - name: Publish to npm + run: npm publish --provenance --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN_MARCUSQUINN }} + + - name: Summary + env: + PKG_VERSION: ${{ steps.version.outputs.version }} + run: | + echo "## npm Package Published" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Version:** $PKG_VERSION" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Install with:" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY + echo "npm install -g aidevops" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY + + update-homebrew-tap: + name: Update Homebrew Tap + runs-on: ubuntu-latest + needs: publish-npm + permissions: + contents: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Get version and SHA + id: release + env: + INPUT_VERSION: ${{ github.event.inputs.version }} + run: | + if [ -n "$INPUT_VERSION" ]; then + VERSION="$INPUT_VERSION" + else + VERSION=$(cat VERSION) + fi + echo "version=$VERSION" >> $GITHUB_OUTPUT + + # Get SHA256 of the release tarball (fail on HTTP errors) + TARBALL_URL="https://github.com/marcusquinn/aidevops/archive/refs/tags/v${VERSION}.tar.gz" + if ! SHA256=$(curl -fsSL "$TARBALL_URL" | sha256sum | cut -d' ' -f1); then + echo "::error::Failed to download tarball for SHA256 calculation" + exit 1 + fi + echo "sha256=$SHA256" >> $GITHUB_OUTPUT + + - name: Update Homebrew formula + env: + RELEASE_VERSION: ${{ steps.release.outputs.version }} + RELEASE_SHA256: ${{ steps.release.outputs.sha256 }} + run: | + # Update the formula with new version and SHA + sed -i "s|url \"https://github.com/marcusquinn/aidevops/archive/refs/tags/v.*\.tar\.gz\"|url \"https://github.com/marcusquinn/aidevops/archive/refs/tags/v${RELEASE_VERSION}.tar.gz\"|" homebrew/aidevops.rb + sed -i "s|sha256 \".*\"|sha256 \"${RELEASE_SHA256}\"|" homebrew/aidevops.rb + + - name: Push to homebrew-tap repo + # Pinned to commit SHA for security + uses: dmnemec/copy_file_to_another_repo_action@bbebd3da22e4a37d04dca5f782edd5201cb97083 + env: + API_TOKEN_GITHUB: ${{ secrets.HOMEBREW_TAP_TOKEN }} + with: + source_file: 'homebrew/aidevops.rb' + destination_repo: 'marcusquinn/homebrew-tap' + destination_folder: 'Formula' + user_email: 'github-actions[bot]@users.noreply.github.com' + user_name: 'github-actions[bot]' + commit_message: 'Update aidevops to v${{ steps.release.outputs.version }}' + continue-on-error: true + + - name: Summary + env: + RELEASE_VERSION: ${{ steps.release.outputs.version }} + RELEASE_SHA256: ${{ steps.release.outputs.sha256 }} + run: | + echo "## Homebrew Tap Updated" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Version:** $RELEASE_VERSION" >> $GITHUB_STEP_SUMMARY + echo "**SHA256:** $RELEASE_SHA256" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Install with:" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY + echo "brew install marcusquinn/tap/aidevops" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Note:** If the tap repo doesn't exist yet, create it first:" >> $GITHUB_STEP_SUMMARY + echo "1. Create repo: marcusquinn/homebrew-tap" >> $GITHUB_STEP_SUMMARY + echo "2. Add Formula/ directory" >> $GITHUB_STEP_SUMMARY + echo "3. Set HOMEBREW_TAP_TOKEN secret with repo write access" >> $GITHUB_STEP_SUMMARY diff --git a/aidevops.sh b/aidevops.sh index 34cf08d6..0cfc6a4e 100755 --- a/aidevops.sh +++ b/aidevops.sh @@ -19,6 +19,9 @@ NC='\033[0m' # No Color # Paths INSTALL_DIR="$HOME/Git/aidevops" AGENTS_DIR="$HOME/.aidevops/agents" +CONFIG_DIR="$HOME/.config/aidevops" +REPOS_FILE="$CONFIG_DIR/repos.json" +# shellcheck disable=SC2034 # Used in fresh install fallback REPO_URL="https://github.com/marcusquinn/aidevops.git" VERSION_FILE="$INSTALL_DIR/VERSION" @@ -67,6 +70,121 @@ check_file() { [[ -f "$1" ]] } +# Initialize repos.json if it doesn't exist +init_repos_file() { + if [[ ! -f "$REPOS_FILE" ]]; then + mkdir -p "$CONFIG_DIR" + echo '{"initialized_repos": []}' > "$REPOS_FILE" + fi + return 0 +} + +# Register a repo in repos.json +# Usage: register_repo +register_repo() { + local repo_path="$1" + local version="$2" + local features="$3" + + init_repos_file + + # Normalize path (resolve symlinks, remove trailing slash) + if ! repo_path=$(cd "$repo_path" 2>/dev/null && pwd -P); then + print_warning "Cannot access path: $repo_path" + return 1 + fi + + if ! command -v jq &>/dev/null; then + print_warning "jq not installed - repo tracking disabled" + return 0 + fi + + # Check if repo already registered + if jq -e --arg path "$repo_path" '.initialized_repos[] | select(.path == $path)' "$REPOS_FILE" &>/dev/null; then + # Update existing entry + local temp_file="${REPOS_FILE}.tmp" + jq --arg path "$repo_path" --arg version "$version" --arg features "$features" \ + '(.initialized_repos[] | select(.path == $path)) |= {path: $path, version: $version, features: ($features | split(",")), updated: (now | strftime("%Y-%m-%dT%H:%M:%SZ"))}' \ + "$REPOS_FILE" > "$temp_file" && mv "$temp_file" "$REPOS_FILE" + else + # Add new entry + local temp_file="${REPOS_FILE}.tmp" + jq --arg path "$repo_path" --arg version "$version" --arg features "$features" \ + '.initialized_repos += [{path: $path, version: $version, features: ($features | split(",")), initialized: (now | strftime("%Y-%m-%dT%H:%M:%SZ"))}]' \ + "$REPOS_FILE" > "$temp_file" && mv "$temp_file" "$REPOS_FILE" + fi + return 0 +} + +# Get list of registered repos +get_registered_repos() { + init_repos_file + + if ! command -v jq &>/dev/null; then + echo "[]" + return 0 + fi + + jq -r '.initialized_repos[] | .path' "$REPOS_FILE" 2>/dev/null || echo "" + return 0 +} + +# Check if a repo needs upgrade (version behind current) +check_repo_needs_upgrade() { + local repo_path="$1" + local current_version + current_version=$(get_version) + + if ! command -v jq &>/dev/null; then + return 1 + fi + + local repo_version + repo_version=$(jq -r --arg path "$repo_path" '.initialized_repos[] | select(.path == $path) | .version' "$REPOS_FILE" 2>/dev/null) + + if [[ -z "$repo_version" || "$repo_version" == "null" ]]; then + return 1 + fi + + # Compare versions (simple string comparison works for semver) + if [[ "$repo_version" != "$current_version" ]]; then + return 0 # needs upgrade + fi + return 1 # up to date +} + +# Detect if current directory has aidevops but isn't registered +detect_unregistered_repo() { + local project_root + + # Check if in a git repo + if ! git rev-parse --is-inside-work-tree &>/dev/null; then + return 1 + fi + + project_root=$(git rev-parse --show-toplevel 2>/dev/null) + + # Check for .aidevops.json + if [[ ! -f "$project_root/.aidevops.json" ]]; then + return 1 + fi + + init_repos_file + + if ! command -v jq &>/dev/null; then + return 1 + fi + + # Check if already registered + if jq -e --arg path "$project_root" '.initialized_repos[] | select(.path == $path)' "$REPOS_FILE" &>/dev/null; then + return 1 # already registered + fi + + # Not registered - return the path + echo "$project_root" + return 0 +} + # Check if on protected branch and offer worktree creation # Returns 0 if safe to proceed, 1 if user cancelled # Sets WORKTREE_PATH if worktree was created @@ -384,29 +502,81 @@ cmd_update() { remote_hash=$(git rev-parse origin/main) if [[ "$local_hash" == "$remote_hash" ]]; then - print_success "Already up to date!" - return 0 - fi - - print_info "Pulling latest changes..." - git pull --ff-only origin main - - if [[ $? -eq 0 ]]; then - local new_version - new_version=$(get_version) - print_success "Updated to version $new_version" - echo "" - print_info "Running setup to apply changes..." - bash "$INSTALL_DIR/setup.sh" + print_success "Framework already up to date!" else - print_error "Failed to pull updates" - print_info "Try: cd $INSTALL_DIR && git pull" - return 1 + print_info "Pulling latest changes..." + if git pull --ff-only origin main; then + local new_version + new_version=$(get_version) + print_success "Updated to version $new_version" + echo "" + print_info "Running setup to apply changes..." + bash "$INSTALL_DIR/setup.sh" + else + print_error "Failed to pull updates" + print_info "Try: cd $INSTALL_DIR && git pull" + return 1 + fi fi else print_warning "Repository not found, performing fresh install..." + # shellcheck disable=SC2312 # curl|bash is intentional for install bash <(curl -fsSL https://raw.githubusercontent.com/marcusquinn/aidevops/main/setup.sh) fi + + # Check registered repos for updates + echo "" + print_header "Checking Initialized Projects" + + local repos_needing_upgrade=() + local current_ver + current_ver=$(get_version) + + while IFS= read -r repo_path; do + [[ -z "$repo_path" ]] && continue + + if [[ -d "$repo_path" ]]; then + if check_repo_needs_upgrade "$repo_path"; then + repos_needing_upgrade+=("$repo_path") + fi + fi + done < <(get_registered_repos) + + if [[ ${#repos_needing_upgrade[@]} -eq 0 ]]; then + print_success "All registered projects are up to date" + else + echo "" + print_warning "${#repos_needing_upgrade[@]} project(s) may need updates:" + for repo in "${repos_needing_upgrade[@]}"; do + local repo_name + repo_name=$(basename "$repo") + echo " - $repo_name ($repo)" + done + echo "" + read -r -p "Update .aidevops.json version in these projects? [y/N] " response + if [[ "$response" =~ ^[Yy]$ ]]; then + for repo in "${repos_needing_upgrade[@]}"; do + if [[ -f "$repo/.aidevops.json" ]]; then + print_info "Updating $repo..." + # Update version in .aidevops.json + if command -v jq &>/dev/null; then + local temp_file="${repo}/.aidevops.json.tmp" + jq --arg version "$current_ver" '.version = $version' "$repo/.aidevops.json" > "$temp_file" && \ + mv "$temp_file" "$repo/.aidevops.json" + + # Update repos.json entry + local features + features=$(jq -r '[.features | to_entries[] | select(.value == true) | .key] | join(",")' "$repo/.aidevops.json" 2>/dev/null || echo "") + register_repo "$repo" "$current_ver" "$features" + + print_success "Updated $(basename "$repo")" + else + print_warning "jq not installed - manual update needed for $repo" + fi + fi + done + fi + fi } # Uninstall command @@ -811,9 +981,11 @@ EOF local gitignore="$project_root/.gitignore" if [[ -f "$gitignore" ]]; then if ! grep -q "^\.agent$" "$gitignore" 2>/dev/null; then - echo "" >> "$gitignore" - echo "# aidevops" >> "$gitignore" - echo ".agent" >> "$gitignore" + { + echo "" + echo "# aidevops" + echo ".agent" + } >> "$gitignore" print_success "Added .agent to .gitignore" fi # Add .beads if beads is enabled @@ -825,6 +997,19 @@ EOF fi fi + # Build features string for registration + local features_list="" + [[ "$enable_planning" == "true" ]] && features_list="${features_list}planning," + [[ "$enable_git_workflow" == "true" ]] && features_list="${features_list}git-workflow," + [[ "$enable_code_quality" == "true" ]] && features_list="${features_list}code-quality," + [[ "$enable_time_tracking" == "true" ]] && features_list="${features_list}time-tracking," + [[ "$enable_database" == "true" ]] && features_list="${features_list}database," + [[ "$enable_beads" == "true" ]] && features_list="${features_list}beads," + features_list="${features_list%,}" # Remove trailing comma + + # Register repo in repos.json + register_repo "$project_root" "$aidevops_version" "$features_list" + echo "" print_success "AI DevOps initialized!" echo "" @@ -1209,6 +1394,243 @@ cmd_update_tools() { bash "$tool_check_script" "$@" } +# Repos command - list and manage registered repos +cmd_repos() { + local action="${1:-list}" + + case "$action" in + list|ls) + print_header "Registered AI DevOps Projects" + echo "" + + init_repos_file + + if ! command -v jq &>/dev/null; then + print_error "jq required for repo management" + return 1 + fi + + local count + count=$(jq '.initialized_repos | length' "$REPOS_FILE" 2>/dev/null || echo "0") + + if [[ "$count" == "0" ]]; then + print_info "No projects registered yet" + echo "" + echo "Initialize a project with: aidevops init" + return 0 + fi + + local current_ver + current_ver=$(get_version) + + jq -r '.initialized_repos[] | "\(.path)|\(.version)|\(.features | join(","))"' "$REPOS_FILE" 2>/dev/null | while IFS='|' read -r path version features; do + local name + name=$(basename "$path") + local status="✓" + local status_color="$GREEN" + + if [[ "$version" != "$current_ver" ]]; then + status="↑" + status_color="$YELLOW" + fi + + if [[ ! -d "$path" ]]; then + status="✗" + status_color="$RED" + fi + + echo -e "${status_color}${status}${NC} ${BOLD}$name${NC}" + echo " Path: $path" + echo " Version: $version" + echo " Features: $features" + echo "" + done + + echo "Legend: ✓ up-to-date ↑ update available ✗ not found" + ;; + + add) + # Register current directory + if ! git rev-parse --is-inside-work-tree &>/dev/null; then + print_error "Not in a git repository" + return 1 + fi + + local project_root + project_root=$(git rev-parse --show-toplevel) + + if [[ ! -f "$project_root/.aidevops.json" ]]; then + print_error "No .aidevops.json found - run 'aidevops init' first" + return 1 + fi + + local version features + if command -v jq &>/dev/null; then + version=$(jq -r '.version' "$project_root/.aidevops.json" 2>/dev/null || echo "unknown") + features=$(jq -r '[.features | to_entries[] | select(.value == true) | .key] | join(",")' "$project_root/.aidevops.json" 2>/dev/null || echo "") + else + version="unknown" + features="" + fi + + register_repo "$project_root" "$version" "$features" + print_success "Registered $(basename "$project_root")" + ;; + + remove|rm) + local repo_path="${2:-}" + local original_path="$repo_path" + + if [[ -z "$repo_path" ]]; then + # Use current directory + if git rev-parse --is-inside-work-tree &>/dev/null; then + repo_path=$(git rev-parse --show-toplevel) + original_path="$repo_path" + else + print_error "Specify a repo path or run from within a git repo" + return 1 + fi + fi + + # Normalize path (keep original if normalization fails) + repo_path=$(cd "$repo_path" 2>/dev/null && pwd -P) || repo_path="$original_path" + + if ! command -v jq &>/dev/null; then + print_error "jq required for repo management" + return 1 + fi + + local temp_file="${REPOS_FILE}.tmp" + jq --arg path "$repo_path" '.initialized_repos |= map(select(.path != $path))' "$REPOS_FILE" > "$temp_file" && \ + mv "$temp_file" "$REPOS_FILE" + + print_success "Removed $repo_path from registry" + ;; + + clean) + # Remove entries for repos that no longer exist + print_info "Cleaning up stale repo entries..." + + if ! command -v jq &>/dev/null; then + print_error "jq required for repo management" + return 1 + fi + + local removed=0 + local temp_file="${REPOS_FILE}.tmp" + + while IFS= read -r repo_path; do + [[ -z "$repo_path" ]] && continue + if [[ ! -d "$repo_path" ]]; then + jq --arg path "$repo_path" '.initialized_repos |= map(select(.path != $path))' "$REPOS_FILE" > "$temp_file" && \ + mv "$temp_file" "$REPOS_FILE" + print_info "Removed: $repo_path" + removed=$((removed + 1)) + fi + done < <(get_registered_repos) + + if [[ $removed -eq 0 ]]; then + print_success "No stale entries found" + else + print_success "Removed $removed stale entries" + fi + ;; + + *) + echo "Usage: aidevops repos " + echo "" + echo "Commands:" + echo " list List all registered projects (default)" + echo " add Register current project" + echo " remove Remove project from registry" + echo " clean Remove entries for non-existent projects" + ;; + esac +} + +# Detect command - check for unregistered aidevops repos +cmd_detect() { + print_header "Detecting AI DevOps Projects" + echo "" + + # Check current directory first + local unregistered + unregistered=$(detect_unregistered_repo) + + if [[ -n "$unregistered" ]]; then + print_info "Found unregistered aidevops project:" + echo " $unregistered" + echo "" + read -r -p "Register this project? [Y/n] " response + response="${response:-y}" + if [[ "$response" =~ ^[Yy]$ ]]; then + local version features + if command -v jq &>/dev/null; then + version=$(jq -r '.version' "$unregistered/.aidevops.json" 2>/dev/null || echo "unknown") + features=$(jq -r '[.features | to_entries[] | select(.value == true) | .key] | join(",")' "$unregistered/.aidevops.json" 2>/dev/null || echo "") + else + version="unknown" + features="" + fi + register_repo "$unregistered" "$version" "$features" + print_success "Registered $(basename "$unregistered")" + fi + return 0 + fi + + # Scan common locations + print_info "Scanning for aidevops projects in ~/Git/..." + + local found=0 + local to_register=() + + if [[ -d "$HOME/Git" ]]; then + while IFS= read -r -d '' aidevops_json; do + local repo_dir + repo_dir=$(dirname "$aidevops_json") + + # Check if already registered + init_repos_file + if command -v jq &>/dev/null; then + if ! jq -e --arg path "$repo_dir" '.initialized_repos[] | select(.path == $path)' "$REPOS_FILE" &>/dev/null; then + to_register+=("$repo_dir") + found=$((found + 1)) + fi + fi + done < <(find "$HOME/Git" -maxdepth 3 -name ".aidevops.json" -print0 2>/dev/null) + fi + + if [[ $found -eq 0 ]]; then + print_success "No unregistered aidevops projects found" + return 0 + fi + + echo "" + print_info "Found $found unregistered project(s):" + for repo in "${to_register[@]}"; do + echo " - $(basename "$repo") ($repo)" + done + + echo "" + read -r -p "Register all? [Y/n] " response + response="${response:-y}" + if [[ "$response" =~ ^[Yy]$ ]]; then + for repo in "${to_register[@]}"; do + local version features + if command -v jq &>/dev/null; then + version=$(jq -r '.version' "$repo/.aidevops.json" 2>/dev/null || echo "unknown") + features=$(jq -r '[.features | to_entries[] | select(.value == true) | .key] | join(",")' "$repo/.aidevops.json" 2>/dev/null || echo "") + else + version="unknown" + features="" + fi + register_repo "$repo" "$version" "$features" + print_success "Registered $(basename "$repo")" + done + fi + return 0 +} + # Help command cmd_help() { local version @@ -1223,8 +1645,11 @@ cmd_help() { echo " upgrade-planning Upgrade TODO.md/PLANS.md to latest templates" echo " features List available features for init" echo " status Check installation status of all components" - echo " update Update aidevops to the latest version" + echo " update Update aidevops to the latest version (alias: upgrade)" + echo " upgrade Alias for update" echo " update-tools Check for outdated tools (--update to auto-update)" + echo " repos [cmd] Manage registered projects (list/add/remove/clean)" + echo " detect Find and register aidevops projects" echo " uninstall Remove aidevops from your system" echo " version Show version information" echo " help Show this help message" @@ -1235,13 +1660,18 @@ cmd_help() { echo " aidevops upgrade-planning # Upgrade planning files to latest" echo " aidevops features # List available features" echo " aidevops status # Check what's installed" - echo " aidevops update # Update aidevops to latest version" + echo " aidevops update # Update framework + check projects" + echo " aidevops repos # List registered projects" + echo " aidevops repos add # Register current project" + echo " aidevops detect # Find unregistered projects" echo " aidevops update-tools # Check for outdated tools" echo " aidevops update-tools -u # Update all outdated tools" echo " aidevops uninstall # Remove aidevops" echo "" - echo "Quick install:" - echo " bash <(curl -fsSL https://raw.githubusercontent.com/marcusquinn/aidevops/main/setup.sh)" + echo "Installation:" + echo " npm install -g aidevops # via npm" + echo " brew install marcusquinn/tap/aidevops # via Homebrew" + echo " bash <(curl -fsSL https://aidevops.sh) # via curl" echo "" echo "Documentation: https://github.com/marcusquinn/aidevops" } @@ -1264,6 +1694,14 @@ cmd_version() { main() { local command="${1:-help}" + # Auto-detect unregistered repo on any command (silent check) + local unregistered + unregistered=$(detect_unregistered_repo 2>/dev/null) + if [[ -n "$unregistered" && "$command" != "detect" && "$command" != "repos" ]]; then + echo -e "${YELLOW}[TIP]${NC} This project uses aidevops but isn't registered. Run: aidevops repos add" + echo "" + fi + case "$command" in init|i) shift @@ -1286,6 +1724,13 @@ main() { shift cmd_upgrade_planning "$@" ;; + repos|projects) + shift + cmd_repos "$@" + ;; + detect|scan) + cmd_detect + ;; uninstall|remove) cmd_uninstall ;; diff --git a/bin/aidevops b/bin/aidevops new file mode 100755 index 00000000..171baf02 --- /dev/null +++ b/bin/aidevops @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +# AI DevOps Framework CLI wrapper +# This wrapper handles both npm global install and local development + +set -euo pipefail + +# Find the actual script location +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Check if we're in an npm global install or local development +if [[ -f "$SCRIPT_DIR/../aidevops.sh" ]]; then + # npm install -g or local development + exec bash "$SCRIPT_DIR/../aidevops.sh" "$@" +elif [[ -f "$HOME/Git/aidevops/aidevops.sh" ]]; then + # Fallback to standard install location + exec bash "$HOME/Git/aidevops/aidevops.sh" "$@" +else + echo "Error: aidevops.sh not found" + echo "Please run: bash <(curl -fsSL https://aidevops.sh)" + exit 1 +fi diff --git a/homebrew/aidevops.rb b/homebrew/aidevops.rb new file mode 100644 index 00000000..79da0668 --- /dev/null +++ b/homebrew/aidevops.rb @@ -0,0 +1,63 @@ +# Homebrew formula for aidevops +# To install: brew install marcusquinn/tap/aidevops +# Or: brew tap marcusquinn/tap && brew install aidevops + +class Aidevops < Formula + desc "AI DevOps Framework - AI-assisted development workflows and automation" + homepage "https://aidevops.sh" + url "https://github.com/marcusquinn/aidevops/archive/refs/tags/v2.52.1.tar.gz" + sha256 "PLACEHOLDER_SHA256" + license "MIT" + head "https://github.com/marcusquinn/aidevops.git", branch: "main" + + depends_on "bash" + depends_on "jq" + depends_on "curl" + + def install + # Install the CLI script + bin.install "aidevops.sh" => "aidevops" + + # Install setup script for manual setup + libexec.install "setup.sh" + + # Install agent files + (share/"aidevops").install ".agent" + (share/"aidevops").install "VERSION" + + # Create wrapper that sets up paths + (bin/"aidevops").write <<~EOS + #!/usr/bin/env bash + export AIDEVOPS_SHARE="#{share}/aidevops" + exec "#{libexec}/aidevops.sh" "$@" + EOS + end + + def post_install + # Run setup to deploy agents (non-interactive) + ENV["AIDEVOPS_NONINTERACTIVE"] = "1" + system "bash", "#{libexec}/setup.sh" + end + + def caveats + <<~EOS + aidevops has been installed! + + Quick start: + aidevops status # Check installation + aidevops init # Initialize in a project + aidevops help # Show all commands + + Agents deployed to: ~/.aidevops/agents/ + + To update: + brew upgrade aidevops + # or + aidevops update + EOS + end + + test do + assert_match "aidevops", shell_output("#{bin}/aidevops version") + end +end diff --git a/package.json b/package.json index 051f9e8e..0cb36611 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,12 @@ { "name": "aidevops", "version": "2.53.0", - "description": "AI DevOps Framework with DSPy, Bun, Elysia, and high-performance tooling", + "description": "AI DevOps Framework - AI-assisted development workflows, code quality, and deployment automation", "type": "module", "main": "index.js", + "bin": { + "aidevops": "./bin/aidevops" + }, "scripts": { "dev": "bun run .opencode/server/api-gateway.ts", "dashboard": "bun run .opencode/server/mcp-dashboard.ts", @@ -15,22 +18,23 @@ "setup": "bun install && npm run install:python", "setup:bun": "bun install", "test": "bun test", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit", + "postinstall": "node scripts/npm-postinstall.js || true" }, "keywords": [ - "dspy", - "dspyground", - "crawl4ai", - "capsolver", - "bun", - "elysia", "ai", "devops", - "prompt-optimization", + "cli", + "automation", + "code-quality", + "git-workflow", + "mcp", "llm", - "mcp" + "dspy", + "prompt-optimization", + "ai-agents" ], - "author": "AI DevOps Framework", + "author": "Marcus Quinn ", "license": "MIT", "dependencies": { "dspyground": "^0.2.0", @@ -46,15 +50,26 @@ "typescript": "^5.0.0" }, "engines": { - "bun": ">=1.0.0", "node": ">=18.0.0" }, + "os": [ + "darwin", + "linux" + ], + "files": [ + "bin/", + "aidevops.sh", + "setup.sh", + "VERSION", + "scripts/npm-postinstall.js", + "templates/" + ], "repository": { "type": "git", - "url": "https://github.com/marcusquinn/aidevops.git" + "url": "git+https://github.com/marcusquinn/aidevops.git" }, "bugs": { "url": "https://github.com/marcusquinn/aidevops/issues" }, - "homepage": "https://github.com/marcusquinn/aidevops#readme" + "homepage": "https://aidevops.sh" } diff --git a/scripts/npm-postinstall.js b/scripts/npm-postinstall.js new file mode 100644 index 00000000..58d9b8c9 --- /dev/null +++ b/scripts/npm-postinstall.js @@ -0,0 +1,59 @@ +/** + * npm postinstall script for aidevops + * Runs setup.sh to deploy agents after npm install -g + */ + +const { execSync } = require('child_process'); +const path = require('path'); +const fs = require('fs'); +const os = require('os'); + +const packageDir = path.resolve(__dirname, '..'); +const setupScript = path.join(packageDir, 'setup.sh'); +const agentsDir = path.join(os.homedir(), '.aidevops', 'agents'); + +// Check if this is a global install +const isGlobalInstall = process.env.npm_config_global === 'true'; + +// Skip if not global install (local dev doesn't need postinstall) +if (!isGlobalInstall && fs.existsSync(agentsDir)) { + console.log('aidevops: Local install detected, skipping setup (agents already deployed)'); + process.exit(0); +} + +// Check if setup.sh exists +if (!fs.existsSync(setupScript)) { + console.log('aidevops: setup.sh not found, skipping postinstall'); + process.exit(0); +} + +console.log('aidevops: Running setup to deploy agents...'); +console.log(''); + +try { + // Run setup.sh non-interactively + execSync(`bash "${setupScript}"`, { + stdio: 'inherit', + cwd: packageDir, + env: { + ...process.env, + // Skip interactive prompts + AIDEVOPS_NONINTERACTIVE: '1' + } + }); + + console.log(''); + console.log('aidevops installed successfully!'); + console.log(''); + console.log('Quick start:'); + console.log(' aidevops status # Check installation'); + console.log(' aidevops init # Initialize in a project'); + console.log(' aidevops help # Show all commands'); + console.log(''); +} catch (error) { + console.error('aidevops: Setup encountered issues (non-critical)'); + console.error(`Run manually: bash "${setupScript}"`); + console.error('Or reinstall with: bash <(curl -fsSL https://aidevops.sh)'); + // Don't fail the install + process.exit(0); +}