From 09bec1d6b90aab7a669df88bf2dabe59fa4fad39 Mon Sep 17 00:00:00 2001 From: Jeff Haynie Date: Thu, 22 Jan 2026 08:07:35 -0600 Subject: [PATCH 01/11] Remove native executable build, use bun global install - Remove bun build --compile native executable building - Switch to bun add -g @agentuity/cli for installation - Rewrite install.sh to use bun global package install - Add installation type detection (global/local/source) - Simplify upgrade command to use bun add -g - Remove executable-related CI workflows and tests - Update canary command to use npm tarballs - Add --verbose flag and AGENTUITY_VERBOSE env var to install.sh - Keep legacy shim at ~/.agentuity/bin for backward compatibility Files deleted: - packages/cli/scripts/build-executables.ts - packages/cli/scripts/entitlements.plist - packages/cli/scripts/test-bundled-create.ts - packages/cli/ALPINE.md - scripts/test-upgrade.sh This eliminates Bun compilation issues while simplifying the installation and upgrade flow. Users need Bun installed anyway to run project commands. Amp-Thread-ID: https://ampcode.com/threads/T-019be5da-0ee0-73cb-bc43-9bb161c9571e Co-authored-by: Amp --- .github/workflows/build.yaml | 125 --- .github/workflows/canary.yaml | 23 - .github/workflows/test-install.yaml | 264 ++--- install.sh | 1077 +++++-------------- packages/cli/ALPINE.md | 66 -- packages/cli/scripts/build-executables.ts | 271 ----- packages/cli/scripts/entitlements.plist | 16 - packages/cli/scripts/test-bundled-create.ts | 244 ----- packages/cli/src/cmd/canary/index.ts | 197 +--- packages/cli/src/cmd/cloud/deploy.ts | 13 +- packages/cli/src/cmd/index.ts | 7 - packages/cli/src/cmd/setup/index.ts | 11 +- packages/cli/src/cmd/upgrade/index.ts | 374 ++----- packages/cli/src/command-prefix.ts | 22 +- packages/cli/src/utils/installation-type.ts | 50 + packages/cli/src/version-check.ts | 31 +- packages/cli/src/version.ts | 2 +- packages/cli/test/upgrade.test.ts | 69 +- scripts/canary.ts | 84 +- scripts/publish.ts | 87 +- scripts/test-upgrade.sh | 319 ------ 21 files changed, 593 insertions(+), 2759 deletions(-) delete mode 100644 packages/cli/ALPINE.md delete mode 100755 packages/cli/scripts/build-executables.ts delete mode 100644 packages/cli/scripts/entitlements.plist delete mode 100755 packages/cli/scripts/test-bundled-create.ts create mode 100644 packages/cli/src/utils/installation-type.ts delete mode 100755 scripts/test-upgrade.sh diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index b99f6f95a..7201eb0b9 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -57,128 +57,3 @@ jobs: bun typecheck # Run only package unit tests, skip integration-suite bun run test:unit - - name: Test Upgrade Command - timeout-minutes: 5 - env: - AGENTUITY_LOG_LEVEL: error - run: | - set -eou pipefail - # Build executables for testing (packages already built from previous step) - cd packages/cli - - # Detect OS and architecture - OS=$(uname -s | tr '[:upper:]' '[:lower:]') - ARCH=$(uname -m) - - # Map architecture names - case "$ARCH" in - x86_64) ARCH="x64" ;; - aarch64|arm64) ARCH="arm64" ;; - esac - - PLATFORM="${OS}-${ARCH}" - BINARY="./dist/bin/agentuity-${PLATFORM}" - - echo "=== Platform Info ===" - echo "OS: $OS" - echo "ARCH: $ARCH" - echo "Platform: $PLATFORM" - echo "Binary: $BINARY" - echo "" - - # Build only for current platform to save time - ./scripts/build-executables.ts --skip-sign --platform="$PLATFORM" - - # Test that upgrade command is available in compiled binary - echo "=== Testing upgrade command visibility in binary ===" - set +e # Don't exit on error yet, capture it - $BINARY --help > /tmp/binary-help.txt 2>&1 - EXIT_CODE=$? - set -e - - # --help exits with code 0 or 1 depending on context, both are OK - if [ $EXIT_CODE -ne 0 ] && [ $EXIT_CODE -ne 1 ]; then - echo "ERROR: Binary --help failed with unexpected exit code $EXIT_CODE" - echo "Output:" - cat /tmp/binary-help.txt - exit 1 - fi - - if grep -q "upgrade" /tmp/binary-help.txt; then - echo "✓ upgrade command found in binary" - else - echo "ERROR: upgrade command not found in binary help" - echo "Full help output:" - cat /tmp/binary-help.txt - exit 1 - fi - - # Test that upgrade command is NOT available when running via bun - echo "=== Testing upgrade command NOT in bun help ===" - set +e # Don't exit on error - ./bin/cli.ts --help > /tmp/bun-help.txt 2>&1 - set -e - - if grep -q "upgrade" /tmp/bun-help.txt; then - echo "ERROR: upgrade command should not be in bun help" - cat /tmp/bun-help.txt - exit 1 - else - echo "✓ upgrade command correctly hidden in bun" - fi - - # Test upgrade --force execution - echo "=== Testing upgrade --force execution ===" - set +e # Capture exit code - AGENTUITY_SKIP_VERSION_CHECK=1 $BINARY upgrade --force > /tmp/upgrade-output.txt 2>&1 - UPGRADE_EXIT_CODE=$? - set -e - - echo "Upgrade exit code: $UPGRADE_EXIT_CODE" - cat /tmp/upgrade-output.txt - - # Check if upgrade ran successfully (exit 0 or output shows expected progress) - # Network failures are acceptable in CI - we just want to verify the command runs - if [ $UPGRADE_EXIT_CODE -eq 0 ]; then - echo "✓ upgrade command completed successfully" - elif grep -q "Successfully upgraded\|Downloading\|Already on latest\|Validating binary" /tmp/upgrade-output.txt; then - echo "✓ upgrade command executed (may have failed at network/install step, but logic ran)" - else - echo "ERROR: upgrade command didn't run properly (exit code: $UPGRADE_EXIT_CODE)" - echo "Expected to see upgrade-related output but got:" - cat /tmp/upgrade-output.txt - exit 1 - fi - - echo "✓ All upgrade command tests passed" - - name: Test Bundled Create Command - timeout-minutes: 5 - env: - AGENTUITY_LOG_LEVEL: error - run: | - set -eou pipefail - cd packages/cli - - # Rebuild executable to include latest code changes (e.g., auth.ts fix) - OS=$(uname -s | tr '[:upper:]' '[:lower:]') - ARCH=$(uname -m) - - case "$ARCH" in - x86_64) ARCH="x64" ;; - aarch64|arm64) ARCH="arm64" ;; - esac - - PLATFORM="${OS}-${ARCH}" - - echo "=== Rebuilding executable with latest changes ===" - ./scripts/build-executables.ts --skip-sign --platform="$PLATFORM" - - BINARY="$(pwd)/dist/bin/agentuity-${PLATFORM}" - - echo "=== Testing bundled create command ===" - echo "Binary: $BINARY" - - # Run bundled create test - bun scripts/test-bundled-create.ts --binary="$BINARY" - - echo "✓ Bundled create command test passed" diff --git a/.github/workflows/canary.yaml b/.github/workflows/canary.yaml index 269f98ef6..ad336464a 100644 --- a/.github/workflows/canary.yaml +++ b/.github/workflows/canary.yaml @@ -49,9 +49,7 @@ jobs: script: | const version = '${{ steps.pack.outputs.prerelease_version }}'; const packagesJson = ${{ steps.pack.outputs.packages_json }}; - const executablesJson = ${{ steps.pack.outputs.executables_json }}; const npmBaseUrl = `https://agentuity-sdk-objects.t3.storage.dev/npm/${version}`; - const binaryBaseUrl = `https://agentuity-sdk-objects.t3.storage.dev/binary/${version}`; // Build packages table let packagesTable = '| Package | Version | URL |\n| --- | --- | --- |\n'; @@ -60,13 +58,6 @@ jobs: packagesTable += `| \`${pkg.name}\` | \`${version}\` | ${url} |\n`; } - // Build executables table - let executablesTable = '| Platform | Version | URL |\n| --- | --- | --- |\n'; - for (const exe of executablesJson) { - const url = `${binaryBaseUrl}/${exe.filename}`; - executablesTable += `| \`${exe.platform}\` | \`${version}\` | ${url} |\n`; - } - // Build dependencies object with all packages const deps = {}; for (const pkg of packagesJson) { @@ -99,20 +90,6 @@ jobs: ${packagesJson.map(pkg => `bun add ${npmBaseUrl}/${pkg.tarball}`).join('\n')} \`\`\` - -
- CLI Executables - - ${executablesTable} -
- -
- Run Canary CLI - - \`\`\`bash - agentuity canary ${version} [command] [...args] - \`\`\` -
`; // Find existing comment diff --git a/.github/workflows/test-install.yaml b/.github/workflows/test-install.yaml index 122706d59..9470445d2 100644 --- a/.github/workflows/test-install.yaml +++ b/.github/workflows/test-install.yaml @@ -8,7 +8,6 @@ on: - 'install.sh' - 'scripts/test-install.sh' - 'scripts/test-bun-check.sh' - - 'scripts/test-upgrade.sh' - 'packages/cli/**' - '.github/workflows/test-install.yaml' pull_request: @@ -16,7 +15,6 @@ on: - 'install.sh' - 'scripts/test-install.sh' - 'scripts/test-bun-check.sh' - - 'scripts/test-upgrade.sh' - '.github/workflows/test-install.yaml' concurrency: @@ -44,6 +42,11 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + - name: Test install script run: | set -e @@ -52,20 +55,23 @@ jobs: tmpdir=$(mktemp -d) export HOME="$tmpdir" - # Get version from package.json to avoid network requests in CI - export VERSION=$(grep -o '"version": "[^"]*"' packages/cli/package.json | head -1 | cut -d'"' -f4) + # Ensure bun is available in the temp HOME + mkdir -p "$HOME/.bun/bin" + ln -s "$(which bun)" "$HOME/.bun/bin/bun" + export PATH="$HOME/.bun/bin:$PATH" # Run install script in non-interactive mode ./install.sh -y - # Verify installation - if [ ! -f "$HOME/.agentuity/bin/agentuity" ]; then - echo "❌ Binary not installed" + # Verify installation via bun global + if [ ! -f "$HOME/.bun/bin/agentuity" ]; then + echo "❌ CLI not installed at $HOME/.bun/bin/agentuity" + ls -la "$HOME/.bun/bin/" || true exit 1 fi - # Test binary execution - "$HOME/.agentuity/bin/agentuity" --version + # Test CLI execution + "$HOME/.bun/bin/agentuity" --version # Cleanup rm -rf "$tmpdir" @@ -81,49 +87,27 @@ jobs: fail-fast: false matrix: distro: - # TODO: Uncomment when Bun fixes musl --compile bug (produces corrupted binaries) - # - name: Alpine Linux (latest) - # image: alpine:latest - # setup: '' - # arch: '' - # - name: Alpine Linux 3.19 - # image: alpine:3.19 - # setup: '' - # arch: '' - # - name: Alpine Linux 3.18 - # image: alpine:3.18 - # setup: '' - # arch: '' - name: Debian 12 (Bookworm) image: debian:12 setup: 'apt-get update -qq && apt-get install -y -qq curl unzip' - arch: '' - name: Debian 11 (Bullseye) image: debian:11 setup: 'apt-get update -qq && apt-get install -y -qq curl unzip' - arch: '' - name: Ubuntu 24.04 image: ubuntu:24.04 setup: 'apt-get update -qq && apt-get install -y -qq curl unzip' - arch: '' - name: Ubuntu 22.04 image: ubuntu:22.04 setup: 'apt-get update -qq && apt-get install -y -qq curl unzip' - arch: '' - name: Ubuntu 20.04 image: ubuntu:20.04 setup: 'apt-get update -qq && apt-get install -y -qq curl unzip' - arch: '' - name: Fedora Latest image: fedora:latest setup: 'dnf install -y -q curl unzip' - arch: '' - # Note: Amazon Linux has curl-minimal pre-installed which conflicts with curl package - # The install script works with curl-minimal, only gzip needs to be installed - name: Amazon Linux 2023 image: amazonlinux:2023 setup: 'yum install -y -q gzip unzip' - arch: '' steps: - name: Checkout @@ -146,8 +130,13 @@ jobs: eval "$SETUP" > /dev/null 2>&1 fi - # Get version from package.json to avoid network requests in CI - export VERSION=$(grep -o '"version": "[^"]*"' packages/cli/package.json | head -1 | cut -d'"' -f4) + # Install Bun first (required for new install script) + echo "Installing Bun..." + curl -fsSL https://bun.sh/install | bash > /dev/null 2>&1 + export PATH="$HOME/.bun/bin:$PATH" + + # Verify bun is installed + bun --version # Run install script if ! ./install.sh -y > /tmp/install.log 2>&1; then @@ -156,20 +145,21 @@ jobs: exit 1 fi - # Verify binary exists - if [ ! -f "$HOME/.agentuity/bin/agentuity" ]; then - echo "❌ Binary not found at $HOME/.agentuity/bin/agentuity" + # Verify CLI is installed + if [ ! -f "$HOME/.bun/bin/agentuity" ]; then + echo "❌ CLI not found at $HOME/.bun/bin/agentuity" + ls -la "$HOME/.bun/bin/" || true exit 1 fi - # Test binary execution - if ! "$HOME/.agentuity/bin/agentuity" --version > /tmp/version.log 2>&1; then - echo "❌ Binary execution failed:" + # Test CLI execution + if ! "$HOME/.bun/bin/agentuity" --version > /tmp/version.log 2>&1; then + echo "❌ CLI execution failed:" cat /tmp/version.log exit 1 fi - version=$("$HOME/.agentuity/bin/agentuity" --version) + version=$("$HOME/.bun/bin/agentuity" --version) echo "✅ SUCCESS: agentuity version $version" EOF @@ -183,20 +173,6 @@ jobs: exit 1 fi - # Run comprehensive test suite - test-suite: - name: Run Test Suite - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Run full test suite - run: | - chmod +x scripts/test-install.sh - ./scripts/test-install.sh - # Test Bun version checking test-bun-checks: name: Test Bun Version Checks @@ -211,31 +187,6 @@ jobs: chmod +x scripts/test-bun-check.sh ./scripts/test-bun-check.sh - # Test upgrade command - test-upgrade: - name: Test Upgrade Command - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Bun - uses: oven-sh/setup-bun@v2 - with: - bun-version: latest - - - name: Install dependencies - run: bun install - - - name: Build packages - run: bun run build - - - name: Run upgrade tests - run: | - chmod +x scripts/test-upgrade.sh - ./scripts/test-upgrade.sh - # Test specific scenarios test-scenarios: name: Test ${{ matrix.scenario.name }} @@ -245,132 +196,107 @@ jobs: fail-fast: false matrix: scenario: - # TODO: Uncomment when Bun fixes musl --compile bug - # - name: Alpine musl-native binary - # test: | - # docker run --rm -v "$PWD:/app" -w /app alpine:latest sh -c ' - # ./install.sh -y > /tmp/install.log 2>&1 - # if ! grep -q "musl libc system" /tmp/install.log; then - # echo "❌ musl detection failed" - # cat /tmp/install.log - # exit 1 - # fi - # $HOME/.agentuity/bin/agentuity --version - # ' - - name: Force reinstall test: | docker run --rm -e CI=true -v "$PWD:/app" -w /app ubuntu:latest sh -c ' apt-get update -qq && apt-get install -y -qq curl unzip > /dev/null 2>&1 - export VERSION=$(grep -o "\"version\": \"[^\"]*\"" packages/cli/package.json | head -1 | cut -d\" -f4) + + # Install Bun + curl -fsSL https://bun.sh/install | bash > /dev/null 2>&1 + export PATH="$HOME/.bun/bin:$PATH" + + # First install ./install.sh -y > /dev/null 2>&1 - first_version=$($HOME/.agentuity/bin/agentuity --version) - ./install.sh -y --force - second_version=$($HOME/.agentuity/bin/agentuity --version) - if [ "$first_version" != "$second_version" ]; then - echo "❌ Version mismatch: $first_version vs $second_version" - exit 1 - fi - echo "✅ Force reinstall completed: $second_version" + first_version=$($HOME/.bun/bin/agentuity --version) + + # Force reinstall + ./install.sh -y --force > /dev/null 2>&1 + second_version=$($HOME/.bun/bin/agentuity --version) + + echo "First version: $first_version" + echo "Second version: $second_version" + echo "✅ Force reinstall completed" ' - name: Non-interactive with CI env test: | docker run --rm -e CI=true -v "$PWD:/app" -w /app ubuntu:latest sh -c ' apt-get update -qq && apt-get install -y -qq curl unzip > /dev/null 2>&1 - export VERSION=$(grep -o "\"version\": \"[^\"]*\"" packages/cli/package.json | head -1 | cut -d\" -f4) + + # Install Bun + curl -fsSL https://bun.sh/install | bash > /dev/null 2>&1 + export PATH="$HOME/.bun/bin:$PATH" + + # Install without -y flag (should still work in CI) ./install.sh > /tmp/install.log 2>&1 - if [ ! -f "$HOME/.agentuity/bin/agentuity" ]; then - echo "❌ Binary not installed" + + if [ ! -f "$HOME/.bun/bin/agentuity" ]; then + echo "❌ CLI not installed" cat /tmp/install.log exit 1 fi - $HOME/.agentuity/bin/agentuity --version + + $HOME/.bun/bin/agentuity --version + echo "✅ Non-interactive install passed" ' - - name: Upgrade command exists + - name: Upgrade command available test: | docker run --rm -e CI=true -v "$PWD:/app" -w /app ubuntu:latest sh -c ' apt-get update -qq && apt-get install -y -qq curl unzip > /dev/null 2>&1 - export VERSION=$(grep -o "\"version\": \"[^\"]*\"" packages/cli/package.json | head -1 | cut -d\" -f4) + + # Install Bun + curl -fsSL https://bun.sh/install | bash > /dev/null 2>&1 + export PATH="$HOME/.bun/bin:$PATH" + + # Install CLI ./install.sh -y > /dev/null 2>&1 - if ! $HOME/.agentuity/bin/agentuity upgrade --help > /tmp/upgrade-help.log 2>&1; then - echo "⚠ Upgrade command not found in current release (expected until new version is published)" + + # Check upgrade command exists + if ! $HOME/.bun/bin/agentuity upgrade --help > /tmp/upgrade-help.log 2>&1; then + echo "⚠ Upgrade command not found (expected until new version is published)" echo "✅ Test passed (pre-release state)" exit 0 fi + if ! grep -q "Upgrade the CLI to the latest version" /tmp/upgrade-help.log; then echo "❌ Upgrade help missing expected text" cat /tmp/upgrade-help.log exit 1 fi + echo "✅ Upgrade command exists" ' - - name: Upgrade version check - test: | - docker run --rm -e CI=true -v "$PWD:/app" -w /app ubuntu:latest sh -c ' - apt-get update -qq && apt-get install -y -qq curl unzip > /dev/null 2>&1 - export VERSION=$(grep -o "\"version\": \"[^\"]*\"" packages/cli/package.json | head -1 | cut -d\" -f4) - ./install.sh -y > /dev/null 2>&1 - version=$($HOME/.agentuity/bin/agentuity --version) - echo "Installed version: $version" - # Try upgrade check (may fail due to network, but command should exist) - if $HOME/.agentuity/bin/agentuity upgrade --json > /tmp/upgrade.log 2>&1 || true; then - echo "✅ Upgrade check completed" - cat /tmp/upgrade.log - else - echo "✅ Upgrade check ran (may have network issues)" - fi - ' - - - name: Upgrade from v0.0.86 to latest + - name: Legacy shim creation test: | docker run --rm -e CI=true -v "$PWD:/app" -w /app ubuntu:latest sh -c ' apt-get update -qq && apt-get install -y -qq curl unzip > /dev/null 2>&1 - # Install old version 0.0.86 - echo "Installing v0.0.86..." - VERSION=0.0.86 ./install.sh -y > /dev/null 2>&1 - old_version=$($HOME/.agentuity/bin/agentuity --version) - echo "Old version: $old_version" + # Install Bun + curl -fsSL https://bun.sh/install | bash > /dev/null 2>&1 + export PATH="$HOME/.bun/bin:$PATH" - if [ "$old_version" != "0.0.86" ]; then - echo "❌ Expected version 0.0.86, got: $old_version" - exit 1 - fi + # Create legacy directory to trigger shim creation + mkdir -p "$HOME/.agentuity/bin" + touch "$HOME/.agentuity/bin/agentuity" - # Check latest version - latest_version=$( (curl -s https://agentuity.sh/release/sdk/version 2>/dev/null | tr -d "v") || echo "unknown") - echo "Latest available version: $latest_version" - - # Upgrade to latest - echo "Upgrading to latest..." - export VERSION=$(grep -o "\"version\": \"[^\"]*\"" packages/cli/package.json | head -1 | cut -d\" -f4) - ./install.sh -y --force > /dev/null 2>&1 - new_version=$($HOME/.agentuity/bin/agentuity --version) - echo "New version: $new_version" + # Install CLI + ./install.sh -y > /dev/null 2>&1 - # Verify version changed (only if newer version exists) - if [ "$new_version" = "$old_version" ]; then - if [ "$latest_version" = "$old_version" ] || [ "$latest_version" = "unknown" ]; then - echo "✅ No newer version available - already on latest ($old_version)" - else - echo "❌ Version did not change (expected $latest_version, got $new_version)" - exit 1 - fi - else - echo "✅ Successfully upgraded from v$old_version to v$new_version" + # Verify shim was created + if [ ! -f "$HOME/.agentuity/bin/agentuity" ]; then + echo "❌ Legacy shim not created" + exit 1 fi - # Verify upgrade command exists in new version (only if we upgraded) - if [ "$new_version" != "$old_version" ]; then - if ! $HOME/.agentuity/bin/agentuity upgrade --help > /dev/null 2>&1; then - echo "⚠ Upgrade command not found in new version (may not be released yet)" - else - echo "✅ Upgrade command exists in new version" - fi + # Verify shim works + if ! "$HOME/.agentuity/bin/agentuity" --version > /dev/null 2>&1; then + echo "❌ Legacy shim does not work" + exit 1 fi + + echo "✅ Legacy shim created and works" ' steps: @@ -388,24 +314,14 @@ jobs: test-summary: name: Test Summary runs-on: ubuntu-latest - needs: - [ - test-native, - test-linux-distros, - test-suite, - test-bun-checks, - test-upgrade, - test-scenarios, - ] + needs: [test-native, test-linux-distros, test-bun-checks, test-scenarios] if: always() steps: - name: Check results run: | if [ "${{ needs.test-native.result }}" != "success" ] || \ [ "${{ needs.test-linux-distros.result }}" != "success" ] || \ - [ "${{ needs.test-suite.result }}" != "success" ] || \ [ "${{ needs.test-bun-checks.result }}" != "success" ] || \ - [ "${{ needs.test-upgrade.result }}" != "success" ] || \ [ "${{ needs.test-scenarios.result }}" != "success" ]; then echo "❌ Some tests failed" exit 1 diff --git a/install.sh b/install.sh index 851e519e2..f192f3919 100755 --- a/install.sh +++ b/install.sh @@ -1,10 +1,11 @@ #!/bin/sh -# adapted from https://raw.githubusercontent.com/sst/opencode/refs/heads/dev/install -# licensed under the same MIT license +# Agentuity CLI installer +# Installs the CLI via Bun global package install set -eu MUTED='\033[0;2m' RED='\033[0;31m' +GREEN='\033[0;32m' CYAN='\033[38;2;0;139;139m' NC='\033[0m' # No Color @@ -15,16 +16,19 @@ requested_version=${VERSION:-} force_install=false non_interactive=false path_modified=false +verbose=false + +# Check for verbose mode via env var +if [ "${AGENTUITY_VERBOSE:-}" = "1" ] || [ "${AGENTUITY_VERBOSE:-}" = "true" ]; then + verbose=true +fi # Restore terminal state on exit/interrupt cleanup_terminal() { - # Restore cursor visibility printf '\033[?25h' 2>/dev/null || true - # Reset terminal to sane state stty sane 2>/dev/null || true } -# Set up global trap for terminal cleanup trap cleanup_terminal EXIT INT TERM # Parse command line arguments @@ -42,6 +46,10 @@ while [ $# -gt 0 ]; do non_interactive=true shift ;; + -v | --verbose) + verbose=true + shift + ;; *) shift ;; @@ -54,29 +62,13 @@ if [ -n "${CI:-}" ] || [ -n "${GITHUB_ACTIONS:-}" ] || [ -n "${GITLAB_CI:-}" ] | fi # Check if we can prompt the user -# When piped (curl ... | sh), stdin is not a TTY but we can still use /dev/tty if [ ! -t 0 ]; then - # stdin is not a TTY (likely piped), check if /dev/tty is available if ! [ -r /dev/tty ] 2>/dev/null; then - # /dev/tty is not readable - truly non-interactive non_interactive=true fi fi -# Check prerequisites - either curl or wget -HAS_CURL=false -HAS_WGET=false -if command -v curl >/dev/null 2>&1; then - HAS_CURL=true -elif command -v wget >/dev/null 2>&1; then - HAS_WGET=true -else - printf "${RED}Error: either curl or wget is required but neither is installed${NC}\n" - exit 1 -fi - raw_os=$(uname -s) -os=$(echo "$raw_os" | tr '[:upper:]' '[:lower:]') case "$raw_os" in Darwin*) os="darwin" ;; Linux*) os="linux" ;; @@ -86,125 +78,20 @@ MINGW* | MSYS* | CYGWIN*) ;; esac -arch=$(uname -m) -if [ "$arch" = "aarch64" ]; then - arch="arm64" -fi -if [ "$arch" = "x86_64" ]; then - arch="x64" -fi - -if [ "$os" = "darwin" ] && [ "$arch" = "x64" ]; then - rosetta_flag=$(sysctl -n sysctl.proc_translated 2>/dev/null || echo 0) - if [ "$rosetta_flag" = "1" ]; then - arch="arm64" - fi -fi - -combo="$os-$arch" -case "$combo" in -linux-x64 | linux-arm64 | darwin-x64 | darwin-arm64) ;; -*) - printf "${RED}Unsupported OS/Arch: $os/$arch${NC}\n" - exit 1 - ;; -esac - -is_musl=false -if [ "$os" = "linux" ]; then - if [ -f /etc/alpine-release ]; then - is_musl=true - fi - - if command -v ldd >/dev/null 2>&1; then - if ldd --version 2>&1 | grep -qi musl; then - is_musl=true - fi - fi -fi - -filename="agentuity-$os-$arch" - -INSTALL_DIR=$HOME/.agentuity/bin -if ! mkdir -p "$INSTALL_DIR" 2>/dev/null; then - printf "${RED}Error: Failed to create installation directory: $INSTALL_DIR${NC}\n" - printf "${RED}Please check permissions and try again${NC}\n" - exit 1 -fi - -if [ ! -w "$INSTALL_DIR" ]; then - printf "${RED}Error: Installation directory is not writable: $INSTALL_DIR${NC}\n" - printf "${RED}Please check permissions and try again${NC}\n" - exit 1 -fi - -if [ -z "$requested_version" ]; then - if [ "$HAS_CURL" = true ]; then - http_response=$(curl --fail --location --connect-timeout 5 --max-time 30 --retry 2 -s -w "\n%{http_code}" https://agentuity.sh/release/sdk/version) - http_code=$(echo "$http_response" | tail -n1) - specific_version=$(echo "$http_response" | sed '$d') - - if [ "$http_code" != "200" ]; then - printf "${RED}Failed to fetch version information (HTTP $http_code)${NC}\n" - printf "${RED}Please try again later or specify a version with VERSION=X.Y.Z${NC}\n" - exit 1 - fi - else - specific_version=$(wget -qO- https://agentuity.sh/release/sdk/version) - if [ $? -ne 0 ]; then - printf "${RED}Failed to fetch version information${NC}\n" - printf "${RED}Please try again later or specify a version with VERSION=X.Y.Z${NC}\n" - exit 1 - fi - fi - - if [ -z "$specific_version" ]; then - printf "${RED}Failed to fetch version information (empty response)${NC}\n" - printf "${RED}Please try again later or specify a version with VERSION=X.Y.Z${NC}\n" - exit 1 - fi - - # Validate the version string format (should be vX.Y.Z or X.Y.Z) - case "$specific_version" in - v[0-9]*.[0-9]*.[0-9]* | [0-9]*.[0-9]*.[0-9]*) - # Valid version format - ;; - *"message"* | *"error"* | *"Error"* | *""* | *"/dev/null 2>&1; then return 0 fi - # Check if bun exists in $HOME/.bun/bin if [ -f "$HOME/.bun/bin/bun" ]; then export PATH="$HOME/.bun/bin:$PATH" + return 0 fi - # Always return 0 - this is a best-effort helper - return 0 + return 1 +} + +# Compare semantic versions: returns 0 if $1 >= $2 +version_gte() { + v1_major=$(echo "$1" | cut -d. -f1) + v1_minor=$(echo "$1" | cut -d. -f2) + v1_patch=$(echo "$1" | cut -d. -f3 | cut -d- -f1) + v2_major=$(echo "$2" | cut -d. -f1) + v2_minor=$(echo "$2" | cut -d. -f2) + v2_patch=$(echo "$2" | cut -d. -f3 | cut -d- -f1) + + if [ "$v1_major" -gt "$v2_major" ]; then return 0; fi + if [ "$v1_major" -lt "$v2_major" ]; then return 1; fi + if [ "$v1_minor" -gt "$v2_minor" ]; then return 0; fi + if [ "$v1_minor" -lt "$v2_minor" ]; then return 1; fi + if [ "$v1_patch" -ge "$v2_patch" ]; then return 0; fi + return 1 } +# Check if Bun is installed and meets minimum version +check_bun() { + ensure_bun_on_path + + if ! command -v bun >/dev/null 2>&1; then + print_message error "Bun is required but not installed." + printf "\n" + print_message info "Install Bun first by running:" + printf "\n" + print_message info " ${CYAN}curl -fsSL https://bun.sh/install | bash${NC}" + printf "\n" + print_message info "Then run this installer again." + exit 1 + fi + + bun_version=$(bun --version 2>/dev/null || echo "0.0.0") + + if ! version_gte "$bun_version" "$MIN_BUN_VERSION"; then + print_message error "Bun version $bun_version is too old. Minimum required: $MIN_BUN_VERSION" + printf "\n" + print_message info "Update Bun by running:" + printf "\n" + print_message info " ${CYAN}bun upgrade${NC}" + exit 1 + fi + + print_message debug "Using Bun version $bun_version" +} + +# Check for legacy Homebrew installation check_brew_install() { if command -v brew >/dev/null 2>&1; then if brew list agentuity >/dev/null 2>&1; then @@ -248,548 +180,247 @@ check_brew_install() { fi ;; *) - print_message error "Please uninstall the Homebrew version first: brew uninstall agentuity" - exit 1 + print_message warning "Keeping Homebrew version. This may cause conflicts." ;; esac else - print_message error "Please uninstall the Homebrew version first: brew uninstall agentuity" - exit 1 + print_message info "${MUTED}Non-interactive mode: run 'brew uninstall agentuity' to remove legacy version${NC}" fi fi fi } -check_bun_install() { - if command -v agentuity >/dev/null 2>&1; then - agentuity_path=$(which agentuity) - - # Check if the binary is in a bun global install location - case "$agentuity_path" in - *"/.bun/bin/"* | *"/bun/bin/"*) - print_message warning "${RED}Warning: ${NC}Bun global installation detected at ${CYAN}$agentuity_path${NC}" - print_message info "${MUTED}The global binary installation is recommended over bun global install.${NC}" - - if [ "$non_interactive" = false ]; then - print_message info "" - print_message info "To switch to the binary installation:" - print_message info " 1. Uninstall the bun global package: ${CYAN}bun remove -g @agentuity/cli${NC}" - print_message info " 2. Re-run this install script" - print_message info "" - printf "Continue anyway? (y/N): " - read -r response /dev/null || read -r response - case "$response" in - [yY][eE][sS] | [yY]) - print_message info "${MUTED}Continuing with installation. Note: You may need to adjust your PATH.${NC}" - ;; - *) - print_message info "Installation cancelled. Please uninstall the bun global package first." - exit 0 - ;; - esac - else - print_message info "${MUTED}Running in non-interactive mode. Installing anyway.${NC}" - print_message info "${MUTED}Note: Ensure $INSTALL_DIR is in your PATH before the bun global path.${NC}" - fi - ;; - esac +# Check for legacy binary installation in ~/.agentuity/bin +check_legacy_binary() { + legacy_bin="$HOME/.agentuity/bin/agentuity" + if [ -f "$legacy_bin" ]; then + # Check if it's an actual binary (not a shim we created) + if file "$legacy_bin" 2>/dev/null | grep -qE "(executable|ELF|Mach-O)"; then + print_message debug "Legacy binary installation detected at $legacy_bin" + print_message debug "This will be replaced with a shim to the new installation." + fi fi } -check_legacy_binaries() { - # First check if agentuity command exists and test if it's the legacy CLI - if command -v agentuity >/dev/null 2>&1; then - agentuity_path=$(which agentuity) +# Install the CLI using bun +install_cli() { + print_message debug "Installing Agentuity CLI..." - # Test if it's the legacy CLI by running 'agentuity ai' (which should fail on legacy) - if agentuity ai >/dev/null 2>&1; then - # Command succeeded, this is the new CLI - no action needed - : - else - # Command failed (exit 1), this is the legacy CLI - print_message warning "${RED}Warning: ${NC}Legacy Go-based CLI detected at ${CYAN}$agentuity_path${NC}" - print_message info "${MUTED}The new TypeScript-based CLI replaces the legacy version.${NC}" - - if [ "$non_interactive" = false ]; then - printf "Do you want to remove the legacy CLI? (y/N): " - read -r response /dev/null || read -r response - case "$response" in - [yY][eE][sS] | [yY]) - if rm -f "$agentuity_path" 2>/dev/null; then - print_message info "${MUTED}Successfully removed legacy CLI${NC}" - else - print_message error "Failed to remove legacy CLI at $agentuity_path" - print_message error "Please remove it manually: rm $agentuity_path" - exit 1 - fi - ;; - *) - print_message error "Please remove the legacy CLI first: rm $agentuity_path" - exit 1 - ;; - esac - else - print_message error "Please remove the legacy CLI first: rm $agentuity_path" - exit 1 - fi + if [ -n "$requested_version" ]; then + # Normalize version (remove 'v' prefix if present) + version=$(echo "$requested_version" | sed 's/^v//') + print_message debug "Installing version $version" + + # Capture output and only show on failure + install_output=$(bun add -g "@agentuity/cli@$version" 2>&1) + install_result=$? + + if [ $install_result -ne 0 ]; then + print_message error "Failed to install @agentuity/cli@$version" + printf "%s\n" "$install_output" + exit 1 fi - fi - - # Also check for legacy install script binaries in known locations - # Legacy install script used these paths (in order of preference): - # $HOME/.local/bin, $HOME/.bin, $HOME/bin, /usr/local/bin - - found_legacy=false - legacy_locations="" - - for path in "$HOME/.local/bin/agentuity" "$HOME/.bin/agentuity" "$HOME/bin/agentuity" "/usr/local/bin/agentuity"; do - if [ -f "$path" ] && [ "$path" != "$INSTALL_DIR/agentuity" ]; then - # Skip if this is the same binary we already handled above - if command -v agentuity >/dev/null 2>&1; then - current_path=$(which agentuity) - if [ "$path" = "$current_path" ]; then - continue - fi - fi - - found_legacy=true - legacy_locations="$legacy_locations $path" + + if [ "$verbose" = "true" ]; then + printf "%s\n" "$install_output" fi - done - - if [ "$found_legacy" = true ]; then - print_message warning "${RED}Warning: ${NC}Legacy binary installation(s) detected" - for location in $legacy_locations; do - print_message info " - ${CYAN}$location${NC}" - done - - if [ "$non_interactive" = false ]; then - printf "Remove legacy binaries? (Y/n): " - read -r response /dev/null || read -r response - case "$response" in - [nN][oO] | [nN]) - print_message info "${MUTED}Skipping legacy binary removal. Note: You may have conflicts.${NC}" - ;; - *) - for location in $legacy_locations; do - if rm -f "$location" 2>/dev/null; then - print_message info "${MUTED}Removed $location${NC}" - else - print_message warning "Could not remove $location - you may need to remove it manually" - fi - done - ;; - esac - else - # Non-interactive mode: auto-remove if writable - for location in $legacy_locations; do - if rm -f "$location" 2>/dev/null; then - print_message info "${MUTED}Removed legacy binary: $location${NC}" - else - print_message warning "Could not remove $location - may require manual cleanup" - fi - done + else + print_message debug "Installing latest version" + + # Capture output and only show on failure + install_output=$(bun add -g @agentuity/cli 2>&1) + install_result=$? + + if [ $install_result -ne 0 ]; then + print_message error "Failed to install @agentuity/cli" + printf "%s\n" "$install_output" + exit 1 + fi + + if [ "$verbose" = "true" ]; then + printf "%s\n" "$install_output" fi fi -} -check_version() { - if command -v agentuity >/dev/null 2>&1; then - agentuity_path=$(which agentuity) - - # Check if it's a legacy CLI - if so, skip version check (will be overwritten) - if ! agentuity ai >/dev/null 2>&1; then - # This is a legacy CLI, skip version check and continue to install - return - fi + print_message debug "Agentuity CLI installed successfully!" +} - installed_version=$(agentuity version 2>/dev/null || echo "unknown") - # Normalize for comparison (strip 'v' prefix from specific_version) - normalized_specific=$(echo "$specific_version" | sed 's/^v//') +# Create a shim in ~/.agentuity/bin for backward compatibility +create_legacy_shim() { + legacy_dir="$HOME/.agentuity/bin" + legacy_bin="$legacy_dir/agentuity" + bun_bin="$HOME/.bun/bin/agentuity" - if [ "$installed_version" != "$normalized_specific" ] && [ "$installed_version" != "unknown" ]; then - print_message info "${MUTED}Installed version: ${NC}$installed_version." - elif [ "$installed_version" = "$normalized_specific" ]; then - if [ "$force_install" = false ]; then - print_message info "${MUTED}Version ${NC}$normalized_specific${MUTED} already installed" - exit 0 - else - print_message info "${MUTED}Force reinstalling version ${NC}$normalized_specific" - fi - fi - fi -} + # Only create shim if ~/.agentuity/bin exists or was previously used + if [ -d "$legacy_dir" ] || [ -f "$legacy_bin" ]; then + mkdir -p "$legacy_dir" -check_musl_and_gcompat() { - if [ "$is_musl" = true ]; then - printf "\n" - print_message warning "${RED}╭────────────────────────────────────────────────────────╮${NC}" - print_message warning "${RED}│${NC} Alpine Linux / musl is NOT currently supported ${RED}│${NC}" - print_message warning "${RED}╰────────────────────────────────────────────────────────╯${NC}" - printf "\n" - print_message info "Bun's --compile produces corrupted binaries on musl (known bug)" - print_message info "Use a glibc distro: Ubuntu, Debian, Fedora, Amazon Linux" - printf "\n" - exit 1 + # Create a shim script that forwards to the bun-installed version + cat > "$legacy_bin" << 'EOF' +#!/bin/sh +exec "$HOME/.bun/bin/agentuity" "$@" +EOF + chmod 755 "$legacy_bin" + print_message debug "Created compatibility shim at $legacy_bin" fi } -version_compare() { - _vc_ver1="$1" - _vc_ver2="$2" - - # Remove 'v' prefix if present - _vc_ver1=$(echo "$_vc_ver1" | sed 's/^v//') - _vc_ver2=$(echo "$_vc_ver2" | sed 's/^v//') - - # Strip prerelease identifiers (e.g., -alpha.1) and build metadata (e.g., +abc123) - # This ensures 1.3.3-alpha.1 is treated as 1.3.3 for comparison - _vc_ver1=$(echo "$_vc_ver1" | sed 's/[-+].*//') - _vc_ver2=$(echo "$_vc_ver2" | sed 's/[-+].*//') - - # Split versions into components - _vc_major1=$(echo "$_vc_ver1" | cut -d. -f1) - _vc_minor1=$(echo "$_vc_ver1" | cut -d. -f2) - _vc_patch1=$(echo "$_vc_ver1" | cut -d. -f3) - - _vc_major2=$(echo "$_vc_ver2" | cut -d. -f1) - _vc_minor2=$(echo "$_vc_ver2" | cut -d. -f2) - _vc_patch2=$(echo "$_vc_ver2" | cut -d. -f3) - - # Validate that all components are numeric - case "$_vc_major1" in '' | *[!0-9]*) return 2 ;; esac - case "$_vc_minor1" in '' | *[!0-9]*) return 2 ;; esac - case "$_vc_patch1" in '' | *[!0-9]*) return 2 ;; esac - case "$_vc_major2" in '' | *[!0-9]*) return 2 ;; esac - case "$_vc_minor2" in '' | *[!0-9]*) return 2 ;; esac - case "$_vc_patch2" in '' | *[!0-9]*) return 2 ;; esac - - # Compare major version - if [ "$_vc_major1" -gt "$_vc_major2" ]; then - return 0 - elif [ "$_vc_major1" -lt "$_vc_major2" ]; then - return 1 - fi - - # Compare minor version - if [ "$_vc_minor1" -gt "$_vc_minor2" ]; then - return 0 - elif [ "$_vc_minor1" -lt "$_vc_minor2" ]; then - return 1 - fi +# Add bun bin to PATH in shell config +add_to_path() { + _atp_config_file=$1 + _atp_command=$2 + _atp_name=${3:-agentuity} - # Compare patch version - if [ "$_vc_patch1" -ge "$_vc_patch2" ]; then - return 0 + if grep -Fxq "$_atp_command" "$_atp_config_file" 2>/dev/null; then + print_message debug "Command already exists in $_atp_config_file, skipping write." + elif [ -w "$_atp_config_file" ]; then + printf "\n# %s\n" "$_atp_name" >>"$_atp_config_file" + printf "%s\n" "$_atp_command" >>"$_atp_config_file" + print_message info "${MUTED}Successfully added ${NC}$_atp_name ${MUTED}to \$PATH in ${NC}$_atp_config_file" + path_modified=true else - return 1 + print_message warning "Manually add the directory to $_atp_config_file (or similar):" + print_message info " $_atp_command" + path_modified=true fi } -check_bun_version() { - # Capture original PATH before we modify it (for later config file checks) - ORIGINAL_PATH="$PATH" - - # First, try to ensure bun is on PATH if it's installed in $HOME/.bun/bin - ensure_bun_on_path +configure_path() { + bun_bin_dir="$HOME/.bun/bin" - # Check if we're in CI mode (auto-install enabled) - is_ci=false - if [ -n "${CI:-}" ] || [ -n "${GITHUB_ACTIONS:-}" ] || [ -n "${GITLAB_CI:-}" ] || [ -n "${CIRCLECI:-}" ] || [ -n "${JENKINS_HOME:-}" ] || [ -n "${TRAVIS:-}" ]; then - is_ci=true - fi + # Check if bun bin is already on PATH + case ":$PATH:" in + *":$bun_bin_dir:"*) + print_message debug "Bun bin directory already on PATH" + return 0 + ;; + esac - if ! command -v bun >/dev/null 2>&1; then - print_message warning "${RED}Bun is not installed${NC}" - print_message info "${MUTED}Bun ${MIN_BUN_VERSION} or higher is required${NC}" - - if [ "$is_ci" = true ]; then - print_message info "${MUTED}CI environment detected - auto-installing Bun...${NC}" - if curl -fsSL https://bun.sh/install | bash; then - print_message info "${MUTED}Bun installed successfully${NC}" - # Add Bun to PATH for the current session - export PATH="$HOME/.bun/bin:$PATH" - print_message info "${MUTED}Continuing with installation...${NC}" - else - print_message error "Failed to install Bun" - exit 1 - fi - elif [ "$non_interactive" = false ]; then - printf "Would you like to install Bun now? (Y/n): " - read -r response /dev/null || read -r response - case "$response" in - [nN][oO] | [nN]) - print_message error "Bun ${MIN_BUN_VERSION} or higher is required to continue" - exit 1 - ;; - *) - print_message info "${MUTED}Installing Bun...${NC}" - if curl -fsSL https://bun.sh/install | bash; then - print_message info "${MUTED}Bun installed successfully${NC}" - # Add Bun to PATH for the current session - export PATH="$HOME/.bun/bin:$PATH" - print_message info "${MUTED}Continuing with installation...${NC}" - else - print_message error "Failed to install Bun" - exit 1 - fi - ;; - esac - else - print_message error "Bun ${MIN_BUN_VERSION} or higher is required to continue" - exit 1 - fi - fi + XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-$HOME/.config} + current_shell=$(basename "${SHELL:-sh}") - # Get installed Bun version - installed_bun_version=$(bun --version 2>/dev/null || echo "unknown") + case $current_shell in + fish) + config_files="$HOME/.config/fish/config.fish" + ;; + zsh) + config_files="$HOME/.zshrc $HOME/.zshenv $XDG_CONFIG_HOME/zsh/.zshrc $XDG_CONFIG_HOME/zsh/.zshenv" + ;; + bash) + config_files="$HOME/.bashrc $HOME/.bash_profile $HOME/.profile $XDG_CONFIG_HOME/bash/.bashrc $XDG_CONFIG_HOME/bash/.bash_profile" + ;; + ash | sh) + config_files="$HOME/.ashrc $HOME/.profile /etc/profile" + ;; + *) + config_files="$HOME/.bashrc $HOME/.bash_profile $XDG_CONFIG_HOME/bash/.bashrc $XDG_CONFIG_HOME/bash/.bash_profile" + ;; + esac - if [ "$installed_bun_version" = "unknown" ]; then - print_message error "Could not determine Bun version" - exit 1 - fi + config_file="" + for file in $config_files; do + if [ -f "$file" ]; then + config_file=$file + break + fi + done - # Check if version meets minimum requirement - if ! version_compare "$installed_bun_version" "$MIN_BUN_VERSION"; then - print_message warning "${RED}Bun version ${installed_bun_version} is installed${NC}" - print_message info "${MUTED}Bun ${MIN_BUN_VERSION} or higher is required${NC}" + if [ -z "$config_file" ]; then + case $current_shell in + fish) config_file="$HOME/.config/fish/config.fish" && mkdir -p "$(dirname "$config_file")" ;; + zsh) config_file="$HOME/.zshrc" ;; + bash) config_file="$HOME/.bashrc" ;; + ash | sh) config_file="$HOME/.profile" ;; + *) config_file="$HOME/.profile" ;; + esac - if [ "$is_ci" = true ]; then - print_message info "${MUTED}CI environment detected - auto-upgrading Bun...${NC}" - if bun upgrade; then - print_message info "${MUTED}Bun upgraded successfully${NC}" - else - print_message error "Failed to upgrade Bun" - exit 1 - fi - elif [ "$non_interactive" = false ]; then - printf "Would you like to upgrade Bun now? (Y/n): " - read -r response /dev/null || read -r response - case "$response" in - [nN][oO] | [nN]) - print_message error "Bun ${MIN_BUN_VERSION} or higher is required to continue" - exit 1 - ;; - *) - print_message info "${MUTED}Upgrading Bun...${NC}" - if bun upgrade; then - print_message info "${MUTED}Bun upgraded successfully${NC}" - else - print_message error "Failed to upgrade Bun" - exit 1 - fi - ;; - esac - else - print_message error "Bun ${MIN_BUN_VERSION} or higher is required to continue" - exit 1 + if [ ! -f "$config_file" ]; then + touch "$config_file" 2>/dev/null || true fi - fi -} -unbuffered_sed() { - if echo | sed -u -e "" >/dev/null 2>&1; then - sed -nu "$@" - elif echo | sed -l -e "" >/dev/null 2>&1; then - sed -nl "$@" - else - _sed_pad="$(printf "\n%512s" "")" - sed -ne "s/$/\\${_sed_pad}/" "$@" + if [ ! -w "$config_file" ]; then + print_message warning "Cannot create or write to $config_file" + print_message info "Manually add to your PATH:" + print_message info " export PATH=$bun_bin_dir:\$PATH" + return 0 + fi fi -} - -print_progress() { - _pp_bytes="$1" - _pp_length="$2" - [ "$_pp_length" -gt 0 ] || return 0 - _pp_width=50 - _pp_percent=$((_pp_bytes * 100 / _pp_length)) - [ "$_pp_percent" -gt 100 ] && _pp_percent=100 - _pp_on=$((_pp_percent * _pp_width / 100)) - _pp_off=$((_pp_width - _pp_on)) - - _pp_filled=$(printf "%*s" "$_pp_on" "" | sed 's/ /■/g') - _pp_empty=$(printf "%*s" "$_pp_off" "" | sed 's/ /・/g') - - printf "\r${CYAN}%s%s %3d%%${NC}" "$_pp_filled" "$_pp_empty" "$_pp_percent" >&4 + case $current_shell in + fish) + add_to_path "$config_file" "fish_add_path $bun_bin_dir" "bun" + ;; + *) + add_to_path "$config_file" "export PATH=$bun_bin_dir:\$PATH" "bun" + ;; + esac } -download_with_progress() { - _dwp_url="$1" - _dwp_output="$2" - - if [ -t 2 ]; then - exec 4>&2 - else - exec 4>/dev/null +# GitHub Actions PATH setup +setup_github_actions() { + if [ -n "${GITHUB_ACTIONS-}" ] && [ "${GITHUB_ACTIONS}" = "true" ]; then + printf "%s\n" "$HOME/.bun/bin" >>"$GITHUB_PATH" + print_message info "Added $HOME/.bun/bin to \$GITHUB_PATH" fi +} - _dwp_tmp_dir=${TMPDIR:-/tmp} - _dwp_basename="${_dwp_tmp_dir}/agentuity_install_$$" - _dwp_tracefile="${_dwp_basename}.trace" - - rm -f "$_dwp_tracefile" - - # Check if mkfifo is available and working - if ! command -v mkfifo >/dev/null 2>&1 || ! mkfifo "$_dwp_tracefile" 2>/dev/null; then - # Fallback to simple download without progress - exec 4>&- - return 1 +show_path_reminder() { + if [ "$path_modified" = true ]; then + printf "\n" + printf "${RED}╭────────────────────────────────────────────────────╮${NC}\n" + printf "${RED}│${NC} ${RED}⚠ ACTION REQUIRED${NC} ${RED}│${NC}\n" + printf "${RED}│${NC} ${RED}│${NC}\n" + printf "${RED}│${NC}${MUTED} Your shell configuration has been updated. ${RED}│${NC}\n" + printf "${RED}│${NC} ${RED}│${NC}\n" + printf "${RED}│ Please restart your terminal or run: │${NC}\n" + printf "${RED}│${NC} ${RED}│${NC}\n" + printf "${RED}│${NC} ${CYAN}source ~/.bashrc${NC} ${RED}│${NC}\n" + printf "${RED}╰────────────────────────────────────────────────────╯${NC}\n" fi - - # Hide cursor - printf "\033[?25l" >&4 - - trap 'rm -f "$_dwp_tracefile"; printf "\033[?25h" >&4 2>/dev/null; exec 4>&- 2>/dev/null; cleanup_terminal' EXIT INT TERM - - ( - curl --trace-ascii "$_dwp_tracefile" --fail --location --connect-timeout 5 --max-time 30 --retry 2 -s -o "$_dwp_output" "$_dwp_url" - ) & - _dwp_curl_pid=$! - - unbuffered_sed \ - -e 'y/ACDEGHLNORTV/acdeghlnortv/' \ - -e '/^0000: content-length:/p' \ - -e '/^<= recv data/p' \ - "$_dwp_tracefile" | - { - _dwp_length=0 - _dwp_bytes=0 - - while IFS=" " read -r _dwp_line; do - set -- $_dwp_line - [ $# -lt 2 ] && continue - _dwp_tag="$1 $2" - - if [ "$_dwp_tag" = "0000: content-length:" ]; then - _dwp_length="$3" - _dwp_length=$(echo "$_dwp_length" | tr -d '\r') - _dwp_bytes=0 - elif [ "$_dwp_tag" = "<= recv" ]; then - _dwp_size="$4" - _dwp_bytes=$((_dwp_bytes + _dwp_size)) - if [ "$_dwp_length" -gt 0 ]; then - print_progress "$_dwp_bytes" "$_dwp_length" - fi - fi - done - } - - wait $_dwp_curl_pid - _dwp_ret=$? - printf "\n" >&4 2>/dev/null - rm -f "$_dwp_tracefile" - printf '\033[?25h' >&4 2>/dev/null - exec 4>&- 2>/dev/null - trap cleanup_terminal EXIT INT TERM - return $_dwp_ret } -download_and_install() { - # Strip 'v' prefix for display - display_version=$(echo "$specific_version" | sed 's/^v//') - print_message info "\n${MUTED}Installing ${NC}agentuity ${MUTED}version: ${NC}$display_version" - tmpdir=$(mktemp -d 2>/dev/null || mktemp -d -t tmp) - - # Ensure cleanup on exit or interrupt - trap 'cd / 2>/dev/null; rm -rf "$tmpdir"; cleanup_terminal; print_message error "Installation cancelled"; exit 130' EXIT INT TERM - - cd "$tmpdir" +run_setup() { + agentuity_bin="$HOME/.bun/bin/agentuity" - # Download compressed file (.gz) - gz_filename="${filename}.gz" - gz_url="${url}.gz" - - # Try download with progress (only works with curl) - download_success=false - if [ "$HAS_CURL" = true ]; then - if download_with_progress "$gz_url" "$gz_filename"; then - download_success=true - elif curl --fail --location --connect-timeout 5 --max-time 30 --retry 2 -# -o "$gz_filename" "$gz_url"; then - download_success=true - fi - else - # wget - try with progress first, fallback to basic if not supported - if wget --help 2>&1 | grep -q -- '--show-progress'; then - wget --show-progress -q -O "$gz_filename" "$gz_url" && download_success=true + if [ -x "$agentuity_bin" ]; then + if [ "$non_interactive" = true ]; then + "$agentuity_bin" setup --non-interactive --setup-token "${SETUP_TOKEN}" || true else - # BusyBox wget doesn't support --show-progress - wget -O "$gz_filename" "$gz_url" && download_success=true + "$agentuity_bin" setup --setup-token "${SETUP_TOKEN}" || true fi + else + print_message warning "Could not run setup - agentuity not found at $agentuity_bin" + print_message info "Try restarting your terminal and running: agentuity setup" fi +} - if [ "$download_success" = false ]; then - print_message error "Failed to download $gz_filename from $gz_url" - exit 1 - fi - - if [ ! -f "$gz_filename" ]; then - print_message error "Download failed - file not found: $gz_filename" - exit 1 - fi - - # Check if gunzip is available - if ! command -v gunzip >/dev/null 2>&1; then - print_message error "gunzip is required but not installed" - print_message error "Please install gzip: yum install gzip (RedHat/Amazon) or apt-get install gzip (Debian/Ubuntu)" - exit 1 - fi - - # Decompress the file - print_message info "${MUTED}Decompressing...${NC}" - if ! gunzip "$gz_filename"; then - print_message error "Failed to decompress $gz_filename" - exit 1 - fi - - if [ ! -f "$filename" ]; then - print_message error "Decompression failed - file not found: $filename" - exit 1 - fi - - # Verify it's a valid binary, not an error page - # Check if file command exists, fallback to checking ELF magic bytes - if command -v file >/dev/null 2>&1; then - if ! file "$filename" 2>/dev/null | grep -q -E "(executable|ELF|Mach-O|PE32)"; then - print_message error "Downloaded file is not a valid executable (possibly a 404 or error page)" - exit 1 - fi +# Progress indicator for installation +show_progress() { + _msg="$1" + if [ -t 1 ]; then + # TTY: show message that will be overwritten + printf "${CYAN}◐${NC} %s" "$_msg" else - # Fallback: check ELF magic bytes (0x7f 'E' 'L' 'F') or Mach-O magic - if ! head -c 4 "$filename" 2>/dev/null | grep -q "^.ELF" && - ! head -c 4 "$filename" 2>/dev/null | od -An -tx1 | grep -q "7f 45 4c 46"; then - print_message error "Downloaded file is not a valid executable (possibly a 404 or error page)" - exit 1 - fi + # Not a TTY: just print the message + printf "%s\n" "$_msg" fi - - cp "$filename" "$INSTALL_DIR/agentuity" - chmod 755 "${INSTALL_DIR}/agentuity" - cd / - rm -rf "$tmpdir" - trap cleanup_terminal EXIT INT TERM } -# Check for Bun installation and version -check_bun_version - -# Check for legacy installations before proceeding -check_brew_install -check_bun_install -check_legacy_binaries - -# Check for musl/Alpine and handle gcompat -check_musl_and_gcompat +clear_progress() { + if [ -t 1 ]; then + # Clear the line + printf "\r\033[K" + fi +} -# NOTE: we will remove this once we are in production! -if [ "${AGENTUITY_HIDE_BANNER:-}" != "true" ]; then +# Show beta banner +show_beta_banner() { printf "\n" printf "${RED}╭─────────────────────────────────────────────────────────────────────╮${NC}\n" printf "${RED}│${NC} ${RED}⚠ v1 BETA BUILD - READY FOR PRODUCTION TESTING${NC} ${RED}│${NC}\n" printf "${RED}├─────────────────────────────────────────────────────────────────────┤${NC}\n" printf "${RED}│${NC} ${RED}│${NC}\n" - printf "${RED}│${NC} This is a Beta build of the upcoming v1 production release. ${RED}│${NC}\n" + printf "${RED}│${NC} This is a Beta build of the upcoming v1 production release. ${RED}│${NC}\n" printf "${RED}│${NC} This build is ${RED}ready for production testing${NC}. ${RED}│${NC}\n" printf "${RED}│${NC} ${RED}│${NC}\n" printf "${RED}│${NC} Please report any issues: ${RED}│${NC}\n" @@ -799,192 +430,46 @@ if [ "${AGENTUITY_HIDE_BANNER:-}" != "true" ]; then printf "${RED}│${NC} ${MUTED}Thank you for your assistance during this final testing period!${NC} ${RED}│${NC}\n" printf "${RED}│${NC} ${RED}│${NC}\n" printf "${RED}╰─────────────────────────────────────────────────────────────────────╯${NC}\n" -fi - -if [ "$force_install" = false ]; then - check_version -fi -download_and_install - -add_to_path() { - _atp_config_file=$1 - _atp_command=$2 - _atp_name=${3:-agentuity} - - if grep -Fxq "$_atp_command" "$_atp_config_file"; then - print_message debug "Command already exists in $_atp_config_file, skipping write." - elif [ -w "$_atp_config_file" ]; then - printf "\n# %s\n" "$_atp_name" >>"$_atp_config_file" - printf "%s\n" "$_atp_command" >>"$_atp_config_file" - print_message info "${MUTED}Successfully added ${NC}$_atp_name ${MUTED}to \$PATH in ${NC}$_atp_config_file" - path_modified=true - else - print_message warning "Manually add the directory to $_atp_config_file (or similar):" - print_message info " $_atp_command" - path_modified=true - fi + printf "\n" } -XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-$HOME/.config} - -current_shell=$(basename "${SHELL:-sh}") -case $current_shell in -fish) - config_files="$HOME/.config/fish/config.fish" - ;; -zsh) - config_files="$HOME/.zshrc $HOME/.zshenv $XDG_CONFIG_HOME/zsh/.zshrc $XDG_CONFIG_HOME/zsh/.zshenv" - ;; -bash) - config_files="$HOME/.bashrc $HOME/.bash_profile $HOME/.profile $XDG_CONFIG_HOME/bash/.bashrc $XDG_CONFIG_HOME/bash/.bash_profile" - ;; -ash) - config_files="$HOME/.ashrc $HOME/.profile /etc/profile" - ;; -sh) - config_files="$HOME/.ashrc $HOME/.profile /etc/profile" - ;; -*) - # Default case if none of the above matches - config_files="$HOME/.bashrc $HOME/.bash_profile $XDG_CONFIG_HOME/bash/.bashrc $XDG_CONFIG_HOME/bash/.bash_profile" - ;; -esac - -config_file="" -for file in $config_files; do - if [ -f "$file" ]; then - config_file=$file - break +# Main installation flow +main() { + # Show beta banner (unless hidden via env var) + if [ "${AGENTUITY_HIDE_BANNER:-}" != "true" ]; then + show_beta_banner fi -done -if [ -z "$config_file" ]; then - # No existing config file found - create one based on shell type - case $current_shell in - fish) - config_file="$HOME/.config/fish/config.fish" - mkdir -p "$(dirname "$config_file")" - ;; - zsh) - config_file="$HOME/.zshrc" - ;; - bash) - config_file="$HOME/.bashrc" - ;; - ash | sh) - config_file="$HOME/.profile" - ;; - *) - config_file="$HOME/.profile" - ;; - esac + # Check prerequisites first (before progress indicator) + check_bun - # Create the file if it doesn't exist - if [ ! -f "$config_file" ]; then - touch "$config_file" 2>/dev/null || true - fi + # Check for legacy installations + check_brew_install + check_legacy_binary - # Verify we can write to it - if [ ! -w "$config_file" ]; then - print_message warning "Cannot create or write to $config_file" - print_message info "Manually add to your PATH:" - print_message info " export PATH=$INSTALL_DIR:\$PATH" - config_file="" - else - print_message info "${MUTED}Created new config file: ${NC}$config_file" - fi -fi + # Show progress indicator + show_progress "Installing Agentuity CLI..." -if [ -n "$config_file" ]; then - # Add bun to PATH if it exists in $HOME/.bun/bin and not already on original PATH - # Use ORIGINAL_PATH to avoid false positive from ensure_bun_on_path temporary addition - bun_bin_dir="$HOME/.bun/bin" - if [ -f "$bun_bin_dir/bun" ]; then - case ":${ORIGINAL_PATH:-$PATH}:" in - *":$bun_bin_dir:"*) - # Bun already on PATH - ;; - *) - case $current_shell in - fish) - add_to_path "$config_file" "fish_add_path $bun_bin_dir" "bun" - ;; - *) - add_to_path "$config_file" "export PATH=$bun_bin_dir:\$PATH" "bun" - ;; - esac - ;; - esac - fi + # Install the CLI + install_cli + + # Create backward compatibility shim if needed + create_legacy_shim - # Add agentuity to PATH if not already on PATH - case ":$PATH:" in - *":$INSTALL_DIR:"*) ;; - *) + # Configure PATH + configure_path - case $current_shell in - fish) - add_to_path "$config_file" "fish_add_path $INSTALL_DIR" - ;; - zsh) - add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH" - ;; - bash) - add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH" - ;; - ash) - add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH" - ;; - sh) - add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH" - ;; - *) - export PATH=$INSTALL_DIR:$PATH - print_message warning "Manually add the directory to your PATH:" - print_message info " export PATH=$INSTALL_DIR:\$PATH" - ;; - esac - ;; - esac -fi + # GitHub Actions setup + setup_github_actions -if [ -n "${GITHUB_ACTIONS-}" ] && [ "${GITHUB_ACTIONS}" = "true" ]; then - printf "%s\n" "$INSTALL_DIR" >>$GITHUB_PATH - print_message info "Added $INSTALL_DIR to \$GITHUB_PATH" -fi + # Clear progress indicator + clear_progress -# Show prominent message if PATH was modified -if [ "$path_modified" = true ]; then - printf "\n" - printf "${RED}╭────────────────────────────────────────────────────╮${NC}\n" - printf "${RED}│${NC} ${RED}⚠ ACTION REQUIRED${NC} ${RED}│${NC}\n" - printf "${RED}│${NC} ${RED}│${NC}\n" - printf "${RED}│${NC}${MUTED} Your shell configuration has been updated. ${RED}│${NC}\n" - printf "${RED}│${NC} ${RED}│${NC}\n" - printf "${RED}│ Please restart your terminal or run: │${NC}\n" - printf "${RED}│${NC} ${RED}│${NC}\n" - - if [ -n "$config_file" ]; then - cmd="source $config_file" - # Box width is 52 (between the borders) - # Command takes: 1 space + cmd length - # We need to pad the rest - padding=$((52 - 1 - ${#cmd})) - printf "${RED}│${NC} ${CYAN}%s${NC}%*s${RED}│${NC}\n" "$cmd" "$padding" "" - else - cmd="export PATH=$INSTALL_DIR:\$PATH" - padding=$((52 - 2 - ${#cmd})) - printf "${RED}│${NC} ${CYAN}%s${NC}%*s${RED}│${NC}\n" "$cmd" "$padding" "" - fi + # Show PATH reminder if needed + show_path_reminder - printf "${RED}╰────────────────────────────────────────────────────╯${NC}\n" -fi + # Run setup + run_setup +} -# Run the setup command to display banner and getting started steps -# Use the full path since PATH may not be updated in the current shell session -# The || true ensures this doesn't fail on older binaries that don't have the setup command -if [ "$non_interactive" = true ]; then - "$INSTALL_DIR/agentuity" setup --non-interactive --setup-token "${SETUP_TOKEN}" || true -else - "$INSTALL_DIR/agentuity" setup --setup-token "${SETUP_TOKEN}" || true -fi +main diff --git a/packages/cli/ALPINE.md b/packages/cli/ALPINE.md deleted file mode 100644 index c74c3050f..000000000 --- a/packages/cli/ALPINE.md +++ /dev/null @@ -1,66 +0,0 @@ -# Alpine Linux Support - -## Current Status - -❌ **Alpine Linux is NOT supported** due to Bun bug with musl `--compile` - -## The Problem - -Bun's `bun build --compile` produces **corrupted binaries on musl** for complex projects: - -- Simple scripts work fine -- Large projects (like Agentuity CLI) produce 100MB files filled with zeros -- The output file shows as "data" instead of ELF executable -- This appears to be a Bun bug specific to large/complex musl compilations - -## What We Tried - -1. ✅ Bun musl runtime works perfectly -2. ✅ Simple `--compile` test scripts work -3. ❌ Full CLI `--compile` produces corrupted zero-filled binaries -4. ❌ gcompat doesn't help (relocation type 37 incompatibility) - -## Installation Behavior - -The install script detects Alpine and exits immediately: - -```bash -curl -fsSL https://agentuity.sh/install | sh -``` - -Output: - -``` -╭────────────────────────────────────────────────────────╮ -│ Alpine Linux / musl is NOT currently supported │ -╰────────────────────────────────────────────────────────╯ - -Bun's --compile produces corrupted binaries on musl (known bug) -Use a glibc distro: Ubuntu, Debian, Fedora, Amazon Linux - -Installation aborted (Alpine Linux not supported) -``` - -## Workaround - -Use a glibc-based distribution or run Agentuity in a Docker container with Ubuntu base: - -```dockerfile -FROM ubuntu:latest -RUN curl -fsSL https://agentuity.sh/install | sh -s -- -y -``` - -## Root Cause Analysis - -Confirmed behavior through testing: - -- **Simple test**: `bun build hello.ts --compile` → Valid ELF executable ✅ -- **Full CLI**: `bun build bin/cli.ts --compile` → 100MB zero-filled file ❌ - -This is a **Bun bug** when compiling large/complex TypeScript projects on musl. - -## Upstream Issue - -This should be reported to Bun: https://github.com/oven-sh/bun/issues - -Until fixed, Alpine Linux users must use glibc-based distributions. diff --git a/packages/cli/scripts/build-executables.ts b/packages/cli/scripts/build-executables.ts deleted file mode 100755 index c55037094..000000000 --- a/packages/cli/scripts/build-executables.ts +++ /dev/null @@ -1,271 +0,0 @@ -#!/usr/bin/env bun - -import { $, file } from 'bun'; -import { join } from 'node:path'; -import { readFile, rm, mkdir } from 'node:fs/promises'; -import { createReadStream, createWriteStream } from 'node:fs'; -import { createGzip } from 'node:zlib'; -import { pipeline } from 'node:stream/promises'; - -const rootDir = join(import.meta.dir, '..'); -const binDir = join(rootDir, 'dist', 'bin'); -const entryPoint = join(rootDir, 'bin', 'cli.ts'); -const entitlementsPath = join(import.meta.dir, 'entitlements.plist'); - -interface Platform { - target: string; - output: string; - needsSign: boolean; -} - -const PLATFORMS: Platform[] = [ - { target: 'bun-linux-arm64', output: 'agentuity-linux-arm64', needsSign: false }, - { target: 'bun-linux-x64', output: 'agentuity-linux-x64', needsSign: false }, - { target: 'bun-darwin-arm64', output: 'agentuity-darwin-arm64', needsSign: true }, - { target: 'bun-darwin-x64', output: 'agentuity-darwin-x64', needsSign: true }, -]; - -function parseArgs() { - const platforms: string[] = []; - let skipSign = false; - let version: string | null = null; - - for (const arg of process.argv.slice(2)) { - if (arg.startsWith('--platform=')) { - platforms.push(arg.slice('--platform='.length)); - } else if (arg === '--skip-sign') { - skipSign = true; - } else if (arg.startsWith('--version=')) { - version = arg.slice('--version='.length); - } else if (arg === '--help' || arg === '-h') { - console.log(` -Usage: bun scripts/build-executables.ts [options] - -Options: - --platform= Build for specific platform (can specify multiple times) - Available: linux-arm64, linux-x64, darwin-arm64, darwin-x64 - --version= Set version for AGENTUITY_CLI_VERSION define (defaults to package.json) - --skip-sign Skip code signing (for testing only - DO NOT USE FOR RELEASES) - --help, -h Show this help message - -Examples: - bun scripts/build-executables.ts # Build all platforms - bun scripts/build-executables.ts --platform=linux-arm64 # Build single platform - bun scripts/build-executables.ts --skip-sign # Build without signing (testing) - -Required Tools: - quill quill signing and notarization tool (https://github.com/anchore/quill) - -Environment Variables (required for signing and notarization): - QUILL_SIGN_P12 Path to P12 certificate file - QUILL_SIGN_PASSWORD Password for P12 certificate - QUILL_NOTARY_KEY Apple notary API key - QUILL_NOTARY_KEY_ID Apple notary key ID - QUILL_NOTARY_ISSUER Apple notary issuer ID - -Note: Linux binaries are glibc-based. Alpine Linux users need gcompat installed. -`); - process.exit(0); - } - } - - return { platforms, skipSign, version }; -} - -async function validateEnvironment(needsSigning: boolean, skipSign: boolean) { - if (needsSigning && !skipSign) { - // Check if quill is installed - try { - await $`which quill`.quiet(); - } catch { - console.error('❌ Error: quill not found.'); - console.error(' Install from: https://github.com/anchore/quill'); - process.exit(1); - } - - // Validate required environment variables - const requiredEnvVars = [ - 'QUILL_SIGN_P12', - 'QUILL_SIGN_PASSWORD', - 'QUILL_NOTARY_KEY', - 'QUILL_NOTARY_KEY_ID', - 'QUILL_NOTARY_ISSUER', - ]; - - const missingVars = requiredEnvVars.filter((varName) => !process.env[varName]); - - if (missingVars.length > 0) { - console.error('❌ Error: Required environment variables not set:'); - for (const varName of missingVars) { - console.error(` - ${varName}`); - } - console.error('\n These are required for quill signing and notarization.'); - console.error(' See: https://github.com/anchore/quill#configuration'); - process.exit(1); - } - - if (process.platform !== 'darwin') { - console.error('❌ Error: macOS code signing required but not running on macOS.'); - console.error(' Either run on macOS or use --skip-sign for testing.'); - process.exit(1); - } - } -} - -async function cleanBinDir() { - console.log('🧹 Cleaning bin directory...'); - try { - await rm(binDir, { recursive: true, force: true }); - } catch { - // Directory might not exist - } - await mkdir(binDir, { recursive: true }); -} - -async function buildExecutable(platform: Platform, version: string) { - const outputPath = join(binDir, platform.output); - console.log(`\n📦 Building ${platform.output} (version ${version})...`); - - try { - // Externalize Vite and its dependencies that contain native modules or complex require patterns - const externals = [ - 'vite', - '@vitejs/plugin-react', - '@hono/vite-dev-server', - '@hono/vite-build', - 'lightningcss', - 'esbuild', - 'rollup', - ]; - const externalArgs = externals.flatMap((pkg) => ['--external', pkg]); - - await $`bun build ${entryPoint} --compile --production --minify --sourcemap --compile-autoload-tsconfig --compile-autoload-package-json ${externalArgs} --target=${platform.target} --outfile=${outputPath} --define AGENTUITY_CLI_VERSION='"${version}"'`.cwd( - rootDir - ); - console.log(`✓ Built ${platform.output}`); - return outputPath; - } catch (err) { - console.error(`✗ Failed to build ${platform.output}:`, err); - throw err; - } -} - -async function signAndNotarizeExecutable(executablePath: string, name: string) { - console.log(`🔐 Signing and notarizing ${name}...`); - - try { - // Use quill to sign and notarize with JIT entitlements for Bun - await $`quill sign-and-notarize ${executablePath} --entitlements ${entitlementsPath}`; - - console.log(`✓ Signed and notarized ${name}`); - } catch (err) { - console.error(`✗ Failed to sign and notarize ${name}:`, err); - throw err; - } -} - -async function compressExecutable(executablePath: string, name: string) { - console.log(`📦 Compressing ${name}...`); - - const gzPath = `${executablePath}.gz`; - - try { - const source = createReadStream(executablePath); - const destination = createWriteStream(gzPath); - const gzip = createGzip({ level: 9 }); // Maximum compression - - await pipeline(source, gzip, destination); - - const originalSize = (await file(executablePath).stat()).size; - const compressedSize = (await file(gzPath).stat()).size; - const savings = ((1 - compressedSize / originalSize) * 100).toFixed(1); - - console.log(`✓ Compressed ${name} (${savings}% smaller)`); - return gzPath; - } catch (err) { - console.error(`✗ Failed to compress ${name}:`, err); - throw err; - } -} - -async function main() { - const { platforms: requestedPlatforms, skipSign, version: cliVersion } = parseArgs(); - - // Get version from CLI arg or package.json - let version = cliVersion; - if (!version) { - const pkgJsonPath = join(rootDir, 'package.json'); - const pkgJson = JSON.parse(await readFile(pkgJsonPath, 'utf-8')); - version = pkgJson.version; - } - - console.log(`📋 Version: ${version}`); - - // Filter platforms if specific ones requested - let platformsToBuild = PLATFORMS; - - if (requestedPlatforms.length > 0) { - platformsToBuild = platformsToBuild.filter((p) => { - const shortName = p.output.replace('agentuity-', ''); - return requestedPlatforms.includes(shortName); - }); - - if (platformsToBuild.length === 0) { - console.error('❌ Error: No valid platforms specified.'); - console.error(' Available: linux-arm64, linux-x64, darwin-arm64, darwin-x64'); - process.exit(1); - } - } - - // Check if any platform needs signing - const needsSigning = platformsToBuild.some((p) => p.needsSign); - - if (skipSign && needsSigning) { - console.log('⚠️ WARNING: --skip-sign is enabled. DO NOT USE FOR PRODUCTION RELEASES.'); - } - - await validateEnvironment(needsSigning, skipSign); - await cleanBinDir(); - - console.log(`\n🚀 Building ${platformsToBuild.length} executable(s)...\n`); - - const builtExecutables: { path: string; platform: Platform }[] = []; - - for (const platform of platformsToBuild) { - const execPath = await buildExecutable(platform, version!); - builtExecutables.push({ path: execPath, platform }); - } - - // Sign and notarize macOS executables - if (!skipSign) { - const macExecutables = builtExecutables.filter((e) => e.platform.needsSign); - if (macExecutables.length > 0) { - console.log('\n🔐 Signing and notarizing macOS executables...\n'); - for (const { path, platform } of macExecutables) { - await signAndNotarizeExecutable(path, platform.output); - } - } - } - - // Compress all executables - console.log('\n📦 Compressing executables...\n'); - const compressedFiles: { path: string; platform: Platform }[] = []; - for (const { path, platform } of builtExecutables) { - const compressedPath = await compressExecutable(path, platform.output); - compressedFiles.push({ path: compressedPath, platform }); - } - - console.log('\n✨ Build complete!\n'); - console.log('Built executables:'); - for (const { path, platform } of compressedFiles) { - const fileInfo = await file(path).stat(); - const sizeMB = (fileInfo.size / 1024 / 1024).toFixed(2); - console.log(` ${platform.output}.gz (${sizeMB} MB)`); - } - console.log(`\nOutput directory: ${binDir}`); -} - -main().catch((err) => { - console.error('Error:', err); - process.exit(1); -}); diff --git a/packages/cli/scripts/entitlements.plist b/packages/cli/scripts/entitlements.plist deleted file mode 100644 index afa54db33..000000000 --- a/packages/cli/scripts/entitlements.plist +++ /dev/null @@ -1,16 +0,0 @@ - - - - - com.apple.security.cs.allow-jit - - com.apple.security.cs.allow-unsigned-executable-memory - - com.apple.security.cs.disable-executable-page-protection - - com.apple.security.cs.allow-dyld-environment-variables - - com.apple.security.cs.disable-library-validation - - - diff --git a/packages/cli/scripts/test-bundled-create.ts b/packages/cli/scripts/test-bundled-create.ts deleted file mode 100755 index 491346e17..000000000 --- a/packages/cli/scripts/test-bundled-create.ts +++ /dev/null @@ -1,244 +0,0 @@ -#!/usr/bin/env bun -/** - * Test bundled executable create command - * - * This test verifies that the bundled executable (not the source CLI) can - * successfully run the create command without missing dependencies like vite. - * - * Prerequisites: - * - Executable must already be built for current platform - * - Run: ./scripts/build-executables.ts --skip-sign --platform= - * - * Usage: - * bun scripts/test-bundled-create.ts - * bun scripts/test-bundled-create.ts --binary=./dist/bin/agentuity-darwin-arm64 - */ - -import { join, resolve } from 'node:path'; -import { existsSync, rmSync, mkdirSync } from 'node:fs'; -import { tmpdir } from 'node:os'; - -// Colors for output -const colors = { - reset: '\x1b[0m', - red: '\x1b[31m', - green: '\x1b[32m', - yellow: '\x1b[33m', - blue: '\x1b[34m', - cyan: '\x1b[36m', -}; - -function log(message: string, color = colors.reset) { - console.log(`${color}${message}${colors.reset}`); -} - -function logStep(step: string) { - log(`\n━━━ ${step} ━━━`, colors.cyan); -} - -function logSuccess(message: string) { - log(`✓ ${message}`, colors.green); -} - -function logError(message: string) { - log(`✗ ${message}`, colors.red); -} - -function logInfo(message: string) { - log(`ℹ ${message}`, colors.blue); -} - -// Detect platform -function detectPlatform(): string { - const os = process.platform === 'darwin' ? 'darwin' : 'linux'; - const arch = process.arch === 'x64' ? 'x64' : 'arm64'; - return `${os}-${arch}`; -} - -// Parse CLI args -function parseArgs(): { binaryPath: string | null } { - const args = process.argv.slice(2); - let binaryPath: string | null = null; - - for (const arg of args) { - if (arg.startsWith('--binary=')) { - binaryPath = arg.split('=')[1]; - } - } - - return { binaryPath }; -} - -async function main() { - log('╭──────────────────────────────────────────────╮', colors.cyan); - log('│ Bundled Executable Create Command Test │', colors.cyan); - log('╰──────────────────────────────────────────────╯', colors.cyan); - - const MONOREPO_ROOT = resolve(import.meta.dir, '../../..'); - const TEMPLATES_DIR = join(MONOREPO_ROOT, 'templates'); - const CLI_ROOT = join(MONOREPO_ROOT, 'packages/cli'); - const TEST_DIR = join(tmpdir(), `agentuity-bundled-test-${Date.now()}`); - const TEST_PROJECT_NAME = 'bundled-test-project'; - const TEST_PROJECT_PATH = join(TEST_DIR, TEST_PROJECT_NAME); - - // Detect or use provided binary path - const { binaryPath: providedBinary } = parseArgs(); - const platform = detectPlatform(); - const binaryPath = providedBinary || join(CLI_ROOT, 'dist', 'bin', `agentuity-${platform}`); - - logInfo(`Platform: ${platform}`); - logInfo(`Binary path: ${binaryPath}`); - logInfo(`Templates dir: ${TEMPLATES_DIR}`); - - // Check binary exists - if (!existsSync(binaryPath)) { - logError(`Binary not found: ${binaryPath}`); - logInfo( - `Build it first: cd ${CLI_ROOT} && ./scripts/build-executables.ts --skip-sign --platform=${platform}` - ); - process.exit(1); - } - - // Check templates exist - if (!existsSync(TEMPLATES_DIR)) { - logError(`Templates directory not found: ${TEMPLATES_DIR}`); - process.exit(1); - } - - // Cleanup any existing test directory - if (existsSync(TEST_DIR)) { - logInfo('Cleaning up existing test directory...'); - rmSync(TEST_DIR, { recursive: true, force: true }); - } - - // Create test directory - mkdirSync(TEST_DIR, { recursive: true }); - - try { - logStep('Step 1: Test bundled executable can run create command'); - - // Run create command with bundled executable - const result = Bun.spawn( - [ - binaryPath, - 'create', - '--name', - TEST_PROJECT_NAME, - '--template-dir', - TEMPLATES_DIR, - '--template', - 'default', // Use default template - '--confirm', - '--no-register', - '--no-install', // Skip install to make test faster - '--no-build', // Skip build to make test faster - ], - { - cwd: TEST_DIR, - stdout: 'pipe', - stderr: 'pipe', - env: { - ...process.env, - AGENTUITY_SKIP_VERSION_CHECK: '1', // Skip version check in CI - }, - } - ); - - const stdout = await new Response(result.stdout).text(); - const stderr = await new Response(result.stderr).text(); - const exitCode = await result.exited; - - // Log output - if (stdout.trim()) { - console.log(stdout); - } - if (stderr.trim()) { - console.error(stderr); - } - - // Check for specific errors that indicate bundling issues - const output = stdout + stderr; - - if (output.includes('Cannot find package')) { - logError('FAILED: Bundled executable is missing dependencies'); - logError('This indicates static imports that should be dynamic'); - process.exit(1); - } - - if (output.includes('error: Cannot find module')) { - logError('FAILED: Bundled executable has module resolution errors'); - process.exit(1); - } - - if (exitCode !== 0) { - logError(`FAILED: Create command exited with code ${exitCode}`); - process.exit(1); - } - - logSuccess('Create command executed without errors'); - - logStep('Step 2: Verify project files were created'); - - // Check that basic files exist - const expectedFiles = [ - join(TEST_PROJECT_PATH, 'package.json'), - join(TEST_PROJECT_PATH, 'agentuity.config.ts'), - join(TEST_PROJECT_PATH, 'src'), - ]; - - let allFilesExist = true; - for (const file of expectedFiles) { - if (!existsSync(file)) { - logError(`Missing file: ${file}`); - allFilesExist = false; - } else { - logSuccess(`Found: ${file}`); - } - } - - if (!allFilesExist) { - logError('FAILED: Not all expected files were created'); - process.exit(1); - } - - logSuccess('All expected files exist'); - - logStep('Step 3: Verify package.json has correct dependencies'); - - const packageJsonPath = join(TEST_PROJECT_PATH, 'package.json'); - const packageJson = JSON.parse(await Bun.file(packageJsonPath).text()); - - // Check for @agentuity dependencies - const hasAgentuityDeps = - packageJson.dependencies?.['@agentuity/runtime'] || - packageJson.dependencies?.['@agentuity/server']; - - if (!hasAgentuityDeps) { - logError('FAILED: package.json missing @agentuity dependencies'); - process.exit(1); - } - - logSuccess('package.json has expected dependencies'); - - // Final cleanup - logStep('Cleanup'); - rmSync(TEST_DIR, { recursive: true, force: true }); - logSuccess('Test directory cleaned up'); - - log('\n╭──────────────────────────────────────────────╮', colors.green); - log('│ ✓ All tests passed! │', colors.green); - log('╰──────────────────────────────────────────────╯', colors.green); - log('', colors.reset); - - process.exit(0); - } catch (error) { - logError(`Test failed with error: ${error}`); - // Cleanup on error - if (existsSync(TEST_DIR)) { - rmSync(TEST_DIR, { recursive: true, force: true }); - } - process.exit(1); - } -} - -main(); diff --git a/packages/cli/src/cmd/canary/index.ts b/packages/cli/src/cmd/canary/index.ts index c03fb5213..b9745fa93 100644 --- a/packages/cli/src/cmd/canary/index.ts +++ b/packages/cli/src/cmd/canary/index.ts @@ -1,22 +1,14 @@ import { createCommand } from '../../types'; -import { getPlatformInfo } from '../upgrade'; -import { downloadWithProgress } from '../../download'; import { z } from 'zod'; import { $ } from 'bun'; -import { join } from 'node:path'; -import { homedir } from 'node:os'; -import { readdir, rm, mkdir, stat } from 'node:fs/promises'; -import { createHash } from 'node:crypto'; import * as tui from '../../tui'; -const CANARY_CACHE_DIR = join(homedir(), '.agentuity', 'canary'); -const CANARY_BASE_URL = 'https://agentuity-sdk-objects.t3.storage.dev/binary'; -const CACHE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000; // 7 days +const CANARY_BASE_URL = 'https://agentuity-sdk-objects.t3.storage.dev/npm'; const CanaryArgsSchema = z.object({ args: z .array(z.string()) - .describe('Version/URL followed by commands to run (e.g., 0.1.6-abc1234 deploy --force)'), + .describe('Version followed by commands to run (e.g., 0.1.6-abc1234 deploy --force)'), }); const CanaryResponseSchema = z.object({ @@ -29,79 +21,6 @@ function isUrl(str: string): boolean { return str.startsWith('http://') || str.startsWith('https://'); } -function getBinaryFilename(platform: { os: string; arch: string }): string { - return `agentuity-${platform.os}-${platform.arch}.gz`; -} - -function getCachePath(version: string): string { - return join(CANARY_CACHE_DIR, version, 'agentuity'); -} - -function hashUrl(url: string): string { - return createHash('sha256').update(url).digest('hex').slice(0, 12); -} - -async function cleanupOldCanaries(): Promise { - try { - await mkdir(CANARY_CACHE_DIR, { recursive: true }); - const entries = await readdir(CANARY_CACHE_DIR); - const now = Date.now(); - - for (const entry of entries) { - const entryPath = join(CANARY_CACHE_DIR, entry); - try { - const stats = await stat(entryPath); - if (now - stats.mtimeMs > CACHE_MAX_AGE_MS) { - await rm(entryPath, { recursive: true, force: true }); - } - } catch { - // Ignore errors for individual entries - } - } - } catch { - // Ignore cleanup errors - } -} - -async function downloadCanary(url: string, destPath: string): Promise { - const destDir = join(destPath, '..'); - await mkdir(destDir, { recursive: true }); - - const gzPath = `${destPath}.gz`; - - const stream = await downloadWithProgress({ - url, - message: 'Downloading canary...', - }); - - const writer = Bun.file(gzPath).writer(); - for await (const chunk of stream) { - writer.write(chunk); - } - await writer.end(); - - if (!(await Bun.file(gzPath).exists())) { - throw new Error('Download failed - file not created'); - } - - try { - await $`gunzip ${gzPath}`.quiet(); - } catch (error) { - if (await Bun.file(gzPath).exists()) { - await $`rm ${gzPath}`.quiet(); - } - throw new Error( - `Decompression failed: ${error instanceof Error ? error.message : 'Unknown error'}` - ); - } - - if (!(await Bun.file(destPath).exists())) { - throw new Error('Decompression failed - file not found'); - } - - await $`chmod 755 ${destPath}`.quiet(); -} - export const command = createCommand({ name: 'canary', description: 'Run a canary version of the CLI', @@ -114,22 +33,18 @@ export const command = createCommand({ }, async handler(ctx) { - const { args } = ctx; + const { args, logger } = ctx; // Get raw args from process.argv to capture ALL args after 'canary ' - // This ensures we forward everything including flags like --json, --force, etc. const argv = process.argv; const canaryIndex = argv.indexOf('canary'); if (args.args.length === 0) { - tui.error('Usage: agentuity canary [commands...]'); + tui.error('Usage: agentuity canary [commands...]'); tui.newline(); tui.info('Examples:'); tui.info(' agentuity canary 0.1.6-abc1234'); tui.info(' agentuity canary 0.1.6-abc1234 deploy --log-level trace'); - tui.info( - ' agentuity canary https://agentuity-sdk-objects.t3.storage.dev/binary/0.1.6-abc1234/agentuity-darwin-arm64.gz' - ); return { executed: false, version: '', @@ -138,77 +53,73 @@ export const command = createCommand({ } // Get target from parsed args, but get forward args from raw argv - // This captures ALL args after the version including any flags const target = args.args[0]; const targetIndex = canaryIndex >= 0 ? argv.indexOf(target, canaryIndex) : -1; const forwardArgs = targetIndex >= 0 ? argv.slice(targetIndex + 1) : args.args.slice(1); - // Clean up old canaries in background - cleanupOldCanaries().catch(() => {}); - - const platform = getPlatformInfo(); let version: string; - let downloadUrl: string; - let cachePath: string; + let tarballUrl: string; if (isUrl(target)) { - // Extract version from URL, or create a unique hash for custom URLs - const match = target.match(/\/binary\/([^/]+)\//); - version = match ? match[1] : `custom-${hashUrl(target)}`; - downloadUrl = target; - cachePath = getCachePath(version); + // Direct URL to tarball + tarballUrl = target; + // Extract version from URL if possible + const match = target.match(/agentuity-cli-(\d+\.\d+\.\d+-[a-f0-9]+)\.tgz/); + version = match ? match[1] : 'custom'; } else { - // Treat as version string + // Version string - construct URL version = target; - const filename = getBinaryFilename(platform); - downloadUrl = `${CANARY_BASE_URL}/${version}/${filename}`; - cachePath = getCachePath(version); + tarballUrl = `${CANARY_BASE_URL}/${version}/agentuity-cli-${version}.tgz`; } - // Check cache - if (await Bun.file(cachePath).exists()) { - tui.info(`Using cached canary ${version}`); - } else { - tui.info(`Downloading canary ${version}...`); - try { - await downloadCanary(downloadUrl, cachePath); - tui.success(`Downloaded canary ${version}`); - } catch (error) { - tui.error( - `Failed to download canary: ${error instanceof Error ? error.message : 'Unknown error'}` - ); + tui.info(`Installing canary CLI version ${version}...`); + logger.debug('Tarball URL: %s', tarballUrl); + + try { + // Install the canary version globally using the tarball URL + const installResult = await $`bun add -g ${tarballUrl}`.quiet().nothrow(); + + if (installResult.exitCode !== 0) { + const stderr = installResult.stderr.toString(); + tui.error(`Failed to install canary version: ${stderr}`); return { executed: false, version, - message: `Failed to download: ${error instanceof Error ? error.message : 'Unknown error'}`, + message: `Installation failed: ${stderr}`, }; } - } - // Update access time - try { - await $`touch -a -m ${cachePath}`.quiet(); - } catch { - // Ignore touch errors - } + tui.success(`Installed canary version ${version}`); - tui.newline(); - tui.info(`Running canary ${version}...`); - tui.newline(); - - // Execute the canary binary with forwarded args - // Skip version check in the canary binary to avoid upgrade prompts - const proc = Bun.spawn([cachePath, ...forwardArgs], { - stdin: 'inherit', - stdout: 'inherit', - stderr: 'inherit', - env: { - ...process.env, - AGENTUITY_SKIP_VERSION_CHECK: '1', - }, - }); - - const exitCode = await proc.exited; - process.exit(exitCode); + // If no additional args, just report success + if (forwardArgs.length === 0) { + tui.info('Run commands with: agentuity '); + return { + executed: true, + version, + message: `Canary version ${version} installed. Use 'agentuity ' to run commands.`, + }; + } + + // Execute the command with the newly installed canary + tui.info(`Running: agentuity ${forwardArgs.join(' ')}`); + tui.newline(); + + const result = await $`agentuity ${forwardArgs}`.nothrow(); + + return { + executed: true, + version, + message: result.exitCode === 0 ? 'Command executed successfully' : 'Command failed', + }; + } catch (error) { + const message = error instanceof Error ? error.message : 'Unknown error'; + tui.error(`Failed to run canary: ${message}`); + return { + executed: false, + version, + message, + }; + } }, }); diff --git a/packages/cli/src/cmd/cloud/deploy.ts b/packages/cli/src/cmd/cloud/deploy.ts index 31e0d1dc6..b638400fc 100644 --- a/packages/cli/src/cmd/cloud/deploy.ts +++ b/packages/cli/src/cmd/cloud/deploy.ts @@ -4,7 +4,6 @@ import { createPublicKey } from 'node:crypto'; import { createReadStream, createWriteStream, existsSync, mkdirSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { StructuredError } from '@agentuity/core'; -import { isRunningFromExecutable } from '../upgrade'; import { createSubcommand, DeployOptionsSchema } from '../../types'; import { getUserAgent } from '../../api'; import * as tui from '../../tui'; @@ -658,18 +657,8 @@ export const deploySubcommand = createSubcommand({ return stepError(errorMsg); } - // Workaround for Bun crash in compiled executables (https://github.com/agentuity/sdk/issues/191) - // Use limited concurrency (1 at a time) for executables to avoid parallel fetch crash - const isExecutable = isRunningFromExecutable(); - const concurrency = isExecutable ? 1 : Math.min(4, build.assets.length); - - if (isExecutable) { - ctx.logger.trace( - `Running from executable - using limited concurrency (${concurrency} uploads at a time)` - ); - } - // Process assets in batches with limited concurrency + const concurrency = Math.min(4, build.assets.length); for (let i = 0; i < build.assets.length; i += concurrency) { const batch = build.assets.slice(i, i + concurrency); const promises: Promise[] = []; diff --git a/packages/cli/src/cmd/index.ts b/packages/cli/src/cmd/index.ts index 065b44a41..bcb028898 100644 --- a/packages/cli/src/cmd/index.ts +++ b/packages/cli/src/cmd/index.ts @@ -1,5 +1,4 @@ import type { CommandDefinition } from '../types'; -import { isRunningFromExecutable } from './upgrade'; // Use dynamic imports for bundler compatibility while maintaining lazy loading export async function discoverCommands(): Promise { @@ -21,14 +20,8 @@ export async function discoverCommands(): Promise { ]); const commands: CommandDefinition[] = []; - const isExecutable = isRunningFromExecutable(); for (const cmd of commandModules) { - // Skip commands that require running from an executable when not in one - if (cmd.executable && !isExecutable) { - continue; - } - commands.push(cmd); // Auto-create hidden top-level aliases for subcommands with toplevel: true diff --git a/packages/cli/src/cmd/setup/index.ts b/packages/cli/src/cmd/setup/index.ts index 81e0398ac..58b7c8d4a 100644 --- a/packages/cli/src/cmd/setup/index.ts +++ b/packages/cli/src/cmd/setup/index.ts @@ -37,15 +37,8 @@ export const command = createCommand({ message: 'Validating your identity', clearOnSuccess: true, callback: async () => { - // For compiled binaries, process.argv contains virtual paths (/$bunfs/root/...) - // Use process.execPath which has the actual binary path - const isCompiledBinary = process.argv[1]?.startsWith('/$bunfs/'); - const cmd = isCompiledBinary - ? [ - process.execPath, - ...process.argv.slice(2).map((x) => (x === 'setup' ? 'login' : x)), - ] - : process.argv.map((x) => (x === 'setup' ? 'login' : x)); + // Re-run the CLI with 'login' instead of 'setup' + const cmd = process.argv.map((x) => (x === 'setup' ? 'login' : x)); const r = Bun.spawn({ cmd: cmd.concat('--json'), stdout: 'pipe', diff --git a/packages/cli/src/cmd/upgrade/index.ts b/packages/cli/src/cmd/upgrade/index.ts index a5084057c..4ac719ab7 100644 --- a/packages/cli/src/cmd/upgrade/index.ts +++ b/packages/cli/src/cmd/upgrade/index.ts @@ -4,41 +4,8 @@ import { getCommand } from '../../command-prefix'; import { z } from 'zod'; import { ErrorCode, createError, exitWithError } from '../../errors'; import * as tui from '../../tui'; -import { downloadWithProgress } from '../../download'; import { $ } from 'bun'; -import { join, dirname } from 'node:path'; -import { tmpdir } from 'node:os'; -import { randomUUID } from 'node:crypto'; -import { access, constants } from 'node:fs/promises'; -import { StructuredError } from '@agentuity/core'; - -export const PermissionError = StructuredError('PermissionError')<{ - binaryPath: string; - reason: string; -}>(); - -async function checkWritePermission(binaryPath: string): Promise { - try { - await access(binaryPath, constants.W_OK); - } catch { - throw new PermissionError({ - binaryPath, - reason: `Cannot write to ${binaryPath}. You may need to run with elevated permissions (e.g., sudo) or reinstall to a user-writable location.`, - message: `Permission denied: Cannot write to ${binaryPath}`, - }); - } - - const parentDir = dirname(binaryPath); - try { - await access(parentDir, constants.W_OK); - } catch { - throw new PermissionError({ - binaryPath, - reason: `Cannot write to directory ${parentDir}. You may need to run with elevated permissions (e.g., sudo) or reinstall to a user-writable location.`, - message: `Permission denied: Cannot write to directory ${parentDir}`, - }); - } -} +import { getInstallationType, type InstallationType } from '../../utils/installation-type'; const UpgradeOptionsSchema = z.object({ force: z.boolean().optional().describe('Force upgrade even if version is the same'), @@ -52,68 +19,17 @@ const UpgradeResponseSchema = z.object({ }); /** - * Check if running from a compiled executable (not via bun/bunx) - * @internal Exported for testing + * Get the installation type - re-exported for backward compatibility + * @deprecated Use getInstallationType() from '../../utils/installation-type' instead */ -export function isRunningFromExecutable(): boolean { - const scriptPath = process.argv[1] || ''; - - // Check if running from compiled binary (uses Bun's virtual filesystem) - // When compiled with `bun build --compile`, the script path is in the virtual /$bunfs/root/ directory - // Note: process.argv[0] is the executable path (e.g., /usr/local/bin/agentuity), not 'bun' - if (scriptPath.startsWith('/$bunfs/root/')) { - return true; - } - - // If running via bun/bunx (from node_modules or .ts files), it's not an executable - if (Bun.main.includes('/node_modules/') || Bun.main.includes('.ts')) { - return false; - } - - // Check if in a bin directory but not in node_modules (globally installed) - const normalized = Bun.main; - const isGlobal = - normalized.includes('/bin/') && - !normalized.includes('/node_modules/') && - !normalized.includes('/packages/cli/bin'); - - return isGlobal; -} +export { getInstallationType, type InstallationType }; /** - * Get the OS and architecture for downloading the binary - * @internal Exported for testing + * Check if running from a global installation + * This replaces the old isRunningFromExecutable() function */ -export function getPlatformInfo(): { os: string; arch: string } { - const platform = process.platform; - const arch = process.arch; - - let os: string; - let archStr: string; - - switch (platform) { - case 'darwin': - os = 'darwin'; - break; - case 'linux': - os = 'linux'; - break; - default: - throw new Error(`Unsupported platform: ${platform}`); - } - - switch (arch) { - case 'x64': - archStr = 'x64'; - break; - case 'arm64': - archStr = 'arm64'; - break; - default: - throw new Error(`Unsupported architecture: ${arch}`); - } - - return { os, arch: archStr }; +export function isGlobalInstall(): boolean { + return getInstallationType() === 'global'; } /** @@ -146,157 +62,47 @@ export async function fetchLatestVersion(): Promise { } /** - * Download the binary for the specified version + * Upgrade the CLI using bun global install */ -async function downloadBinary( - version: string, - platform: { os: string; arch: string } -): Promise { - const { os, arch } = platform; - const url = `https://agentuity.sh/release/sdk/${version}/${os}/${arch}`; - - const tmpDir = tmpdir(); - const tmpFile = join(tmpDir, `agentuity-${randomUUID()}`); - const gzFile = `${tmpFile}.gz`; - - const stream = await downloadWithProgress({ - url, - message: `Downloading version ${version}...`, - }); - - // Write to temp file - const writer = Bun.file(gzFile).writer(); - for await (const chunk of stream) { - writer.write(chunk); - } - await writer.end(); +async function performBunUpgrade(version: string): Promise { + // Remove 'v' prefix for npm version + const npmVersion = version.replace(/^v/, ''); - // Verify file was downloaded - if (!(await Bun.file(gzFile).exists())) { - throw new Error('Download failed - file not created'); - } - - // Decompress using gunzip - try { - await $`gunzip ${gzFile}`.quiet(); - } catch (error) { - if (await Bun.file(gzFile).exists()) { - await $`rm ${gzFile}`.quiet(); - } - throw new Error( - `Decompression failed: ${error instanceof Error ? error.message : 'Unknown error'}` - ); - } - - // Verify decompressed file exists - if (!(await Bun.file(tmpFile).exists())) { - throw new Error('Decompression failed - file not found'); - } + // Use bun to install the specific version globally + const result = await $`bun add -g @agentuity/cli@${npmVersion}`.quiet().nothrow(); - // Verify it's a valid binary - const fileType = await $`file ${tmpFile}`.text(); - if (!fileType.match(/(executable|ELF|Mach-O|PE32)/i)) { - throw new Error('Downloaded file is not a valid executable'); + if (result.exitCode !== 0) { + const stderr = result.stderr.toString(); + throw new Error(`Failed to install @agentuity/cli@${npmVersion}: ${stderr}`); } - - // Make executable - await $`chmod 755 ${tmpFile}`.quiet(); - - return tmpFile; } /** - * Validate the downloaded binary by running version command + * Verify the upgrade was successful by checking the installed version */ -async function validateBinary(binaryPath: string, expectedVersion: string): Promise { - try { - // Use spawn to capture both stdout and stderr - const proc = Bun.spawn([binaryPath, 'version'], { - stdout: 'pipe', - stderr: 'pipe', - }); - - const [stdout, stderr] = await Promise.all([ - new Response(proc.stdout).text(), - new Response(proc.stderr).text(), - ]); - - const exitCode = await proc.exited; - - if (exitCode !== 0) { - const errorDetails = []; - if (stdout.trim()) errorDetails.push(`stdout: ${stdout.trim()}`); - if (stderr.trim()) errorDetails.push(`stderr: ${stderr.trim()}`); - const details = errorDetails.length > 0 ? `\n${errorDetails.join('\n')}` : ''; - throw new Error(`Failed with exit code ${exitCode}${details}`); - } - - const actualVersion = stdout.trim(); - - // Normalize versions for comparison (remove 'v' prefix) - const normalizedExpected = expectedVersion.replace(/^v/, ''); - const normalizedActual = actualVersion.replace(/^v/, ''); +async function verifyUpgrade(expectedVersion: string): Promise { + // Run agentuity version to check the installed version + const result = await $`agentuity version`.quiet().nothrow(); - if (normalizedActual !== normalizedExpected) { - throw new Error(`Version mismatch: expected ${expectedVersion}, got ${actualVersion}`); - } - } catch (error) { - if (error instanceof Error) { - throw new Error(`Binary validation failed: ${error.message}`); - } - throw new Error('Binary validation failed'); + if (result.exitCode !== 0) { + throw new Error('Failed to verify upgrade - could not run agentuity version'); } -} - -/** - * Replace the current binary with the new one - * Uses platform-specific safe replacement strategies - */ -async function replaceBinary(newBinaryPath: string, currentBinaryPath: string): Promise { - const platform = process.platform; - if (platform === 'darwin' || platform === 'linux') { - // Unix: Use atomic move via temp file - const backupPath = `${currentBinaryPath}.backup`; - const tempPath = `${currentBinaryPath}.new`; + const installedVersion = result.stdout.toString().trim(); + const normalizedExpected = expectedVersion.replace(/^v/, ''); + const normalizedInstalled = installedVersion.replace(/^v/, ''); - try { - // Copy new binary to temp location next to current binary - await $`cp ${newBinaryPath} ${tempPath}`.quiet(); - await $`chmod 755 ${tempPath}`.quiet(); - - // Backup current binary - if (await Bun.file(currentBinaryPath).exists()) { - await $`cp ${currentBinaryPath} ${backupPath}`.quiet(); - } - - // Atomic rename - await $`mv ${tempPath} ${currentBinaryPath}`.quiet(); - - // Clean up backup after successful replacement - if (await Bun.file(backupPath).exists()) { - await $`rm ${backupPath}`.quiet(); - } - } catch (error) { - // Try to restore backup if replacement failed - if (await Bun.file(backupPath).exists()) { - await $`mv ${backupPath} ${currentBinaryPath}`.quiet(); - } - // Clean up temp file if it exists - if (await Bun.file(tempPath).exists()) { - await $`rm ${tempPath}`.quiet(); - } - throw error; - } - } else { - throw new Error(`Unsupported platform for binary replacement: ${platform}`); + if (normalizedInstalled !== normalizedExpected) { + throw new Error( + `Version mismatch after upgrade: expected ${normalizedExpected}, got ${normalizedInstalled}` + ); } } export const command = createCommand({ name: 'upgrade', description: 'Upgrade the CLI to the latest version', - executable: true, + hidden: false, // Always visible, but handler checks installation type skipUpgradeCheck: true, tags: ['update'], examples: [ @@ -318,9 +124,35 @@ export const command = createCommand({ const { logger, options } = ctx; const { force } = ctx.opts; + const installationType = getInstallationType(); const currentVersion = getVersion(); - // Use process.execPath to get the actual file path (Bun.main is virtual for compiled binaries) - const currentBinaryPath = process.execPath; + + // Check if we can upgrade based on installation type + if (installationType === 'source') { + tui.error('Upgrade is not available when running from source.'); + tui.info('You are running the CLI from source code (development mode).'); + tui.info('Use git to update the source code instead.'); + return { + upgraded: false, + from: currentVersion, + to: currentVersion, + message: 'Cannot upgrade: running from source', + }; + } + + if (installationType === 'local') { + tui.error('Upgrade is not available for local project installations.'); + tui.info('The CLI is installed as a project dependency.'); + tui.newline(); + tui.info('To upgrade, update your package.json or run:'); + tui.info(` ${tui.muted('bun add @agentuity/cli@latest')}`); + return { + upgraded: false, + from: currentVersion, + to: currentVersion, + message: 'Cannot upgrade: local project installation', + }; + } try { // Fetch latest version @@ -359,39 +191,6 @@ export const command = createCommand({ tui.newline(); } - // Check write permissions before prompting - fail early with helpful message - try { - await checkWritePermission(currentBinaryPath); - } catch (error) { - if (error instanceof PermissionError) { - tui.error('Unable to upgrade: permission denied'); - tui.newline(); - tui.warning(`The CLI binary at ${tui.bold(error.binaryPath)} is not writable.`); - tui.newline(); - if (process.env.AGENTUITY_RUNTIME) { - console.log('You cannot self-upgrade the agentuity cli in the cloud runtime.'); - console.log('The runtime will automatically update the cli and other software'); - console.log('within a day or so. If you need assistance, please contact us'); - console.log('at support@agentuity.com.'); - } else { - console.log('To fix this, you can either:'); - console.log( - ` 1. Run with elevated permissions: ${tui.muted('sudo agentuity upgrade')}` - ); - console.log(` 2. Reinstall to a user-writable location`); - } - tui.newline(); - exitWithError( - createError(ErrorCode.PERMISSION_DENIED, 'Upgrade failed: permission denied', { - path: error.binaryPath, - }), - logger, - options.errorFormat - ); - } - throw error; - } - // Confirm upgrade if (!force) { const shouldUpgrade = await tui.confirm('Do you want to upgrade?', true); @@ -408,39 +207,30 @@ export const command = createCommand({ } } - // Get platform info - const platform = getPlatformInfo(); - - // Download binary - const tmpBinaryPath = await tui.spinner({ - type: 'progress', - message: 'Downloading...', - callback: async () => await downloadBinary(latestVersion, platform), - }); - - // Validate binary + // Perform the upgrade using bun await tui.spinner({ - message: 'Validating binary...', - callback: async () => await validateBinary(tmpBinaryPath, latestVersion), + message: `Installing @agentuity/cli@${normalizedLatest}...`, + callback: async () => await performBunUpgrade(latestVersion), }); - // Replace binary + // Verify the upgrade await tui.spinner({ - message: 'Installing...', - callback: async () => await replaceBinary(tmpBinaryPath, currentBinaryPath), + message: 'Verifying installation...', + callback: async () => await verifyUpgrade(latestVersion), }); - // Clean up temp file - if (await Bun.file(tmpBinaryPath).exists()) { - await $`rm ${tmpBinaryPath}`.quiet(); - } - const message = normalizedCurrent === normalizedLatest - ? `Successfully upgraded to ${normalizedLatest}` + ? `Successfully reinstalled ${normalizedLatest}` : `Successfully upgraded from ${normalizedCurrent} to ${normalizedLatest}`; tui.success(message); + // Hint about PATH if needed + tui.newline(); + tui.info( + `${tui.muted('If the new version is not detected, restart your terminal or run:')} source ~/.bashrc` + ); + return { upgraded: true, from: currentVersion, @@ -448,27 +238,11 @@ export const command = createCommand({ message, }; } catch (error) { - let errorDetails: Record = { + const errorDetails: Record = { error: error instanceof Error ? error.message : 'Unknown error', + installationType, }; - if (error instanceof Error && error.message.includes('Binary validation failed')) { - const match = error.message.match( - /Failed with exit code (\d+)\n(stdout: .+\n)?(stderr: .+)?/s - ); - if (match) { - const exitCode = match[1]; - const stdout = match[2]?.replace('stdout: ', '').trim(); - const stderr = match[3]?.replace('stderr: ', '').trim(); - - errorDetails = { - validation_exit_code: exitCode, - ...(stdout && { validation_stdout: stdout }), - ...(stderr && { validation_stderr: stderr }), - }; - } - } - exitWithError( createError(ErrorCode.INTERNAL_ERROR, 'Upgrade failed', errorDetails), logger, diff --git a/packages/cli/src/command-prefix.ts b/packages/cli/src/command-prefix.ts index 4674ec8fe..6304f5caa 100644 --- a/packages/cli/src/command-prefix.ts +++ b/packages/cli/src/command-prefix.ts @@ -1,34 +1,20 @@ -import path from 'node:path'; import { getPackageName } from './version'; +import { getInstallationType } from './utils/installation-type'; let cachedPrefix: string | null = null; /** * Detects how the CLI is being invoked and returns the appropriate command prefix. - * Returns "agentuity" if installed globally, or "bunx @agentuity/cli" if running via bunx. + * Returns "agentuity" if installed globally, or "bunx @agentuity/cli" if running locally. */ export function getCommandPrefix(): string { if (cachedPrefix) { return cachedPrefix; } - // Check if running from a globally installed package - // When installed globally, the process.argv[1] will be in a bin directory - const scriptPath = process.argv[1] || ''; - const normalized = path.normalize(scriptPath); + const installationType = getInstallationType(); - const isCompiledBinary = - process.argv[0] === 'bun' && scriptPath.startsWith('/$bunfs/root/agentuity-'); - - // If we have AGENTUITY_CLI_VERSION set we are running from compiled binary OR - // If the script is in node_modules/.bin or a global bin directory, it's likely global - const isGlobal = - isCompiledBinary || - (normalized.includes(`${path.sep}bin${path.sep}`) && - !normalized.includes(`${path.sep}node_modules${path.sep}`) && - !normalized.includes(path.join('packages', 'cli', 'bin'))); - - if (isGlobal) { + if (installationType === 'global') { cachedPrefix = 'agentuity'; } else { // Running locally via bunx or from source diff --git a/packages/cli/src/utils/installation-type.ts b/packages/cli/src/utils/installation-type.ts new file mode 100644 index 000000000..98765af2f --- /dev/null +++ b/packages/cli/src/utils/installation-type.ts @@ -0,0 +1,50 @@ +/** + * Detects how the CLI was installed and is being run + */ + +export type InstallationType = 'global' | 'local' | 'source'; + +/** + * Determines the installation type based on how the CLI is being executed + * + * @returns 'global' - Installed globally via `bun add -g @agentuity/cli` + * @returns 'local' - Running from project's node_modules (bunx or direct) + * @returns 'source' - Running from source code (development) + */ +export function getInstallationType(): InstallationType { + const mainPath = Bun.main; + + // Global install: ~/.bun/install/global/node_modules/@agentuity/cli/... + if (mainPath.includes('/.bun/install/global/')) { + return 'global'; + } + + // Local project install: ./node_modules/@agentuity/cli/... + if (mainPath.includes('/node_modules/@agentuity/cli/')) { + return 'local'; + } + + // Source/development: packages/cli/bin/cli.ts or similar + return 'source'; +} + +/** + * Check if running from a global installation + */ +export function isGlobalInstall(): boolean { + return getInstallationType() === 'global'; +} + +/** + * Check if running from a local project installation + */ +export function isLocalInstall(): boolean { + return getInstallationType() === 'local'; +} + +/** + * Check if running from source (development mode) + */ +export function isSourceInstall(): boolean { + return getInstallationType() === 'source'; +} diff --git a/packages/cli/src/version-check.ts b/packages/cli/src/version-check.ts index 27ef7ecbd..388a609f4 100644 --- a/packages/cli/src/version-check.ts +++ b/packages/cli/src/version-check.ts @@ -1,5 +1,6 @@ import type { Config, Logger, CommandDefinition } from './types'; -import { isRunningFromExecutable, fetchLatestVersion } from './cmd/upgrade'; +import { getInstallationType } from './utils/installation-type'; +import { fetchLatestVersion } from './cmd/upgrade'; import { getVersion, getCompareUrl, getReleaseUrl, toTag } from './version'; import * as tui from './tui'; import { saveConfig } from './config'; @@ -22,8 +23,9 @@ function shouldSkipCheck( commandDef: CommandDefinition | undefined, args: string[] ): boolean { - // Skip if running via bun/bunx (not installed executable) - if (!isRunningFromExecutable()) { + // Skip if not a global installation (can't auto-upgrade local/source installs) + const installationType = getInstallationType(); + if (installationType !== 'global') { return true; } @@ -135,25 +137,28 @@ async function updateCheckTimestamp(config: Config | null, logger: Logger): Prom } /** - * Perform the upgrade and re-run the command + * Perform the upgrade using bun global install and re-run the command */ -async function performUpgrade(logger: Logger): Promise { +async function performUpgrade(logger: Logger, targetVersion: string): Promise { try { - // Run upgrade command with --force since user already confirmed via prompt - // Use process.execPath to get the actual binary path (not Bun.main which is virtual) - logger.info('Starting upgrade...'); - await $`${process.execPath} upgrade --force`.quiet(); + // Remove 'v' prefix for npm version + const npmVersion = targetVersion.replace(/^v/, ''); + + logger.info('Upgrading to version %s...', npmVersion); + + // Use bun to install the specific version globally + await $`bun add -g @agentuity/cli@${npmVersion}`.quiet(); // If we got here, the upgrade succeeded // Re-run the original command with the new binary const args = process.argv.slice(2); - const newBinaryPath = process.execPath; logger.info('Upgrade successful! Restarting with new version...'); console.log(''); - // Spawn new process with same arguments - const proc = Bun.spawn([newBinaryPath, ...args], { + // Spawn new process using the global agentuity command + // This will use the newly installed version + const proc = Bun.spawn(['agentuity', ...args], { stdin: 'inherit', stdout: 'inherit', stderr: 'inherit', @@ -231,7 +236,7 @@ export async function checkForUpdates( } // User wants to upgrade - perform it - await performUpgrade(logger); + await performUpgrade(logger, latestVersion); } catch (error) { // Non-fatal - if we can't fetch the latest version (network error, timeout, etc.), // just log at debug level and continue without interrupting the user's command diff --git a/packages/cli/src/version.ts b/packages/cli/src/version.ts index b2efcd91e..15e90faa0 100644 --- a/packages/cli/src/version.ts +++ b/packages/cli/src/version.ts @@ -16,7 +16,7 @@ export function getPackage(): typeof pkg { } export function getVersion(): string { - return process.env.AGENTUITY_CLI_VERSION || getPackage().version || 'dev'; + return getPackage().version || 'dev'; } export function getPackageName(): string { diff --git a/packages/cli/test/upgrade.test.ts b/packages/cli/test/upgrade.test.ts index ab056fca3..38c630c78 100644 --- a/packages/cli/test/upgrade.test.ts +++ b/packages/cli/test/upgrade.test.ts @@ -1,39 +1,20 @@ import { describe, test, expect } from 'bun:test'; -import { isRunningFromExecutable, getPlatformInfo, PermissionError } from '../src/cmd/upgrade'; +import { getInstallationType, isGlobalInstall } from '../src/cmd/upgrade'; describe('upgrade command', () => { - test('isRunningFromExecutable returns false when running from bun script', () => { - const result = isRunningFromExecutable(); - expect(typeof result).toBe('boolean'); - expect(result).toBe(false); + test('getInstallationType returns source when running from test', () => { + const result = getInstallationType(); + expect(typeof result).toBe('string'); + // When running tests from source, should return 'source' + expect(result).toBe('source'); }); - test('getPlatformInfo returns valid platform info', () => { - const platform = getPlatformInfo(); - expect(platform).toHaveProperty('os'); - expect(platform).toHaveProperty('arch'); - expect(['darwin', 'linux']).toContain(platform.os); - expect(['x64', 'arm64']).toContain(platform.arch); + test('isGlobalInstall returns false when running from source', () => { + const result = isGlobalInstall(); + expect(typeof result).toBe('boolean'); + expect(result).toBe(false); }); - test.skipIf(process.platform !== 'darwin' || process.arch !== 'arm64')( - 'getPlatformInfo returns darwin arm64', - () => { - const platform = getPlatformInfo(); - expect(platform.os).toBe('darwin'); - expect(platform.arch).toBe('arm64'); - } - ); - - test.skipIf(process.platform !== 'linux' || process.arch !== 'x64')( - 'getPlatformInfo returns linux x64', - () => { - const platform = getPlatformInfo(); - expect(platform.os).toBe('linux'); - expect(platform.arch).toBe('x64'); - } - ); - test('should validate version format', () => { const validVersions = ['v1.2.3', '1.2.3', 'v0.0.1', '10.20.30']; const invalidVersions = ['error', 'message', '', ' { expect(needsUpgrade).toBe(shouldUpgrade); } }); - - test('should construct correct download URL', () => { - const version = 'v1.2.3'; - const os = 'darwin'; - const arch = 'arm64'; - const url = `https://agentuity.sh/release/sdk/${version}/${os}/${arch}`; - - expect(url).toBe('https://agentuity.sh/release/sdk/v1.2.3/darwin/arm64'); - }); - - test('PermissionError has correct properties', () => { - const error = new PermissionError({ - binaryPath: '/usr/local/bin/agentuity', - reason: 'Cannot write to file', - message: 'Permission denied: Cannot write to file', - }); - expect(error.name).toBe('PermissionError'); - expect(error.binaryPath).toBe('/usr/local/bin/agentuity'); - expect(error.reason).toBe('Cannot write to file'); - expect(error.message).toBe('Permission denied: Cannot write to file'); - }); - - test('PermissionError is an instance of Error', () => { - const error = new PermissionError({ - binaryPath: '/usr/local/bin/agentuity', - reason: 'test', - }); - expect(error instanceof Error).toBe(true); - expect(error instanceof PermissionError).toBe(true); - }); }); diff --git a/scripts/canary.ts b/scripts/canary.ts index c9021122b..941edb120 100755 --- a/scripts/canary.ts +++ b/scripts/canary.ts @@ -7,8 +7,6 @@ import { $ } from 'bun'; const rootDir = join(import.meta.dir, '..'); const packagesDir = join(rootDir, 'packages'); const distDir = join(rootDir, 'dist', 'packs'); -const cliDir = join(rootDir, 'packages', 'cli'); -const binDir = join(cliDir, 'dist', 'bin'); interface PackageInfo { name: string; @@ -17,11 +15,6 @@ interface PackageInfo { tarball?: string; } -interface ExecutableInfo { - platform: string; - filename: string; -} - async function readJSON(path: string) { const content = await readFile(path, 'utf-8'); return JSON.parse(content); @@ -144,25 +137,6 @@ async function createManifest(version: string, packages: PackageInfo[]) { console.log(`\n✓ Created manifest.json`); } -async function buildExecutables(version: string): Promise { - console.log(`\n🔨 Building CLI executables...\n`); - - await $`bun scripts/build-executables.ts --version=${version} --skip-sign`.cwd(cliDir); - - const executables: ExecutableInfo[] = []; - const files = await readdir(binDir); - - for (const file of files) { - if (file.endsWith('.gz')) { - const platform = file.replace('agentuity-', '').replace('.gz', ''); - executables.push({ platform, filename: file }); - console.log(` ✓ Built ${file}`); - } - } - - return executables; -} - async function uploadPackagesToS3(version: string, dryRun: boolean) { const s3Path = `s3://agentuity-sdk-objects/npm/${version}`; @@ -190,38 +164,8 @@ async function uploadPackagesToS3(version: string, dryRun: boolean) { } } -async function uploadExecutablesToS3( - version: string, - executables: ExecutableInfo[], - dryRun: boolean -) { - const s3Path = `s3://agentuity-sdk-objects/binary/${version}`; - - const expiresDate = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); - const expires = expiresDate.toISOString(); - - console.log(`\n☁️ Uploading executables to ${s3Path}...\n`); - console.log(` Objects will expire: ${expires}\n`); - - if (dryRun) { - console.log(' [DRY RUN] Would upload:'); - for (const exe of executables) { - console.log(` ${exe.filename} → ${s3Path}/${exe.filename}`); - } - return; - } - - for (const exe of executables) { - const filePath = join(binDir, exe.filename); - console.log(` Uploading ${exe.filename}...`); - await $`aws s3 cp ${filePath} ${s3Path}/${exe.filename} --expires ${expires} --acl public-read`; - console.log(` ✓ Uploaded ${exe.filename}`); - } -} - -function printTable(version: string, packages: PackageInfo[], executables: ExecutableInfo[]) { +function printTable(version: string, packages: PackageInfo[]) { const npmBaseUrl = `https://agentuity-sdk-objects.t3.storage.dev/npm/${version}`; - const binaryBaseUrl = `https://agentuity-sdk-objects.t3.storage.dev/binary/${version}`; console.log('\n📋 Package Summary:\n'); console.log('| Package | Version | URL |'); @@ -230,14 +174,6 @@ function printTable(version: string, packages: PackageInfo[], executables: Execu const url = `${npmBaseUrl}/${pkg.tarball}`; console.log(`| \`${pkg.name}\` | \`${version}\` | ${url} |`); } - - console.log('\n📋 Executable Summary:\n'); - console.log('| Platform | Version | URL |'); - console.log('| --- | --- | --- |'); - for (const exe of executables) { - const url = `${binaryBaseUrl}/${exe.filename}`; - console.log(`| \`${exe.platform}\` | \`${version}\` | ${url} |`); - } console.log(''); } @@ -283,15 +219,12 @@ Description: 3. Updates all package.json files with the new version 4. Builds packages (unless --no-build) 5. Runs npm pack for each publishable package - 6. Builds CLI executables for all platforms - 7. Uploads packages to S3 npm/{version}/ with 7-day expiration - 8. Uploads executables to S3 binary/{version}/ with 7-day expiration - 9. Reverts package.json changes (unless --no-revert or CI) + 6. Uploads packages to S3 npm/{version}/ with 7-day expiration + 7. Reverts package.json changes (unless --no-revert or CI) In CI mode (GITHUB_OUTPUT set), outputs: - prerelease_version: The version string - packages_json: JSON array of {name, tarball} objects - - executables_json: JSON array of {platform, filename} objects Examples: bun scripts/canary.ts # Test locally (dry run) @@ -343,14 +276,9 @@ async function main() { await createManifest(prereleaseVersion, packed); - // Build CLI executables - const executables = await buildExecutables(prereleaseVersion); - - // Upload packages and executables to S3 await uploadPackagesToS3(prereleaseVersion, dryRun); - await uploadExecutablesToS3(prereleaseVersion, executables, dryRun); - printTable(prereleaseVersion, packed, executables); + printTable(prereleaseVersion, packed); // Write GitHub Actions outputs await writeGitHubOutput('prerelease_version', prereleaseVersion); @@ -358,10 +286,6 @@ async function main() { 'packages_json', JSON.stringify(packed.map((p) => ({ name: p.name, tarball: p.tarball }))) ); - await writeGitHubOutput( - 'executables_json', - JSON.stringify(executables.map((e) => ({ platform: e.platform, filename: e.filename }))) - ); console.log('✅ Workflow completed successfully!\n'); diff --git a/scripts/publish.ts b/scripts/publish.ts index c812a3659..c2112c9bd 100755 --- a/scripts/publish.ts +++ b/scripts/publish.ts @@ -55,21 +55,12 @@ Description: GitHub Release: - Creates/updates GitHub release with generated release notes - - Builds and uploads CLI executables for multiple platforms - Builds and uploads VS Code extension (.vsix) for manual installation - Marks pre-releases appropriately on GitHub -Required Environment Variables: - QUILL_SIGN_P12 Path to P12 certificate file - QUILL_SIGN_PASSWORD Password for P12 certificate - QUILL_NOTARY_KEY Apple notary API key - QUILL_NOTARY_KEY_ID Apple notary key ID - QUILL_NOTARY_ISSUER Apple notary issuer ID - Required Tools: gh GitHub CLI (https://cli.github.com/) amp Amp CLI for release notes generation - quill quill signing and notarization tool (https://github.com/anchore/quill) Examples: bun scripts/publish.ts # Publish to npm (interactive) @@ -413,42 +404,6 @@ async function validateEnvironment(isDryRun: boolean) { console.error(' Required for generating release notes.'); process.exit(1); } - - // Check for quill CLI - try { - await $`quill --version`.quiet(); - } catch { - console.error('❌ Error: quill not found.'); - console.error(' Install from: https://github.com/anchore/quill'); - process.exit(1); - } - - // Validate quill environment variables - const requiredEnvVars = [ - 'QUILL_SIGN_P12', - 'QUILL_SIGN_PASSWORD', - 'QUILL_NOTARY_KEY', - 'QUILL_NOTARY_KEY_ID', - 'QUILL_NOTARY_ISSUER', - ]; - - const missingVars = requiredEnvVars.filter((varName) => !process.env[varName]); - - if (missingVars.length > 0) { - console.error('❌ Error: Required environment variables not set:'); - for (const varName of missingVars) { - console.error(` - ${varName}`); - } - console.error('\n These are required for quill signing and notarization.'); - console.error(' See: https://github.com/anchore/quill#configuration'); - process.exit(1); - } - - if (process.platform !== 'darwin') { - console.error('❌ Error: macOS code signing required but not running on macOS.'); - console.error(' Must run publish on macOS for signing and notarization.'); - process.exit(1); - } } console.log('✓ Environment validation passed\n'); @@ -519,22 +474,6 @@ Formatting Instructions: } } -async function buildExecutables(version: string, skipSign: boolean) { - console.log('\n🔨 Building CLI executables...\n'); - - const cliDir = join(rootDir, 'packages', 'cli'); - try { - const args = ['scripts/build-executables.ts', `--version=${version}`]; - if (skipSign) { - args.push('--skip-sign'); - } - await $`bun ${args}`.cwd(cliDir); - } catch (err) { - console.error('✗ Failed to build executables:', err); - throw err; - } -} - async function buildVSCodeExtension(version: string): Promise { console.log('\n🧩 Building VS Code extension...\n'); @@ -584,21 +523,6 @@ async function createOrUpdateGitHubRelease( args.push('--prerelease'); } - // Add executable assets (only .gz compressed files) - const binDir = join(rootDir, 'packages', 'cli', 'dist', 'bin'); - const executables = await readdir(binDir); - const assetFiles: string[] = []; - for (const exe of executables) { - if (exe.endsWith('.gz')) { - assetFiles.push(join(binDir, exe)); - } - } - - // Add VS Code extension if provided - if (vsixPath) { - assetFiles.push(vsixPath); - } - // First create the release without assets try { console.log(' Creating release...'); @@ -609,12 +533,12 @@ async function createOrUpdateGitHubRelease( throw err; } - // Then upload assets one by one with progress - for (const assetPath of assetFiles) { - const assetName = assetPath.split('/').pop(); + // Upload VS Code extension if provided + if (vsixPath) { + const assetName = vsixPath.split('/').pop(); console.log(` Uploading ${assetName}...`); try { - await $`gh release upload ${tag} ${assetPath} --clobber`.cwd(rootDir); + await $`gh release upload ${tag} ${vsixPath} --clobber`.cwd(rootDir); console.log(` ✓ Uploaded ${assetName}`); } catch (err) { console.error(`✗ Failed to upload ${assetName}:`, err); @@ -717,9 +641,6 @@ async function main() { console.log('\n🔨 Running bun run build...'); await $`bun run build`.cwd(rootDir); - // Build executables (skip signing in dry-run) - await buildExecutables(newVersion, isDryRun); - // Build VS Code extension const vsixPath = await buildVSCodeExtension(newVersion); diff --git a/scripts/test-upgrade.sh b/scripts/test-upgrade.sh deleted file mode 100755 index d75870041..000000000 --- a/scripts/test-upgrade.sh +++ /dev/null @@ -1,319 +0,0 @@ -#!/bin/sh -set -e - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -INSTALL_SCRIPT="$REPO_ROOT/install.sh" -CLI_DIR="$REPO_ROOT/packages/cli" -CLI_BIN="$CLI_DIR/bin/cli.ts" -CLI_COMPILED="$CLI_DIR/agentuity" - -# Colors -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -print_header() { - printf "\n${BLUE}╭─────────────────────────────────────────────────────╮${NC}\n" - printf "${BLUE}│${NC} %-50s ${BLUE} │${NC}\n" "$1" - printf "${BLUE}╰─────────────────────────────────────────────────────╯${NC}\n\n" -} - -print_success() { - printf "${GREEN}✓${NC} %s\n" "$1" -} - -print_error() { - printf "${RED}✗${NC} %s\n" "$1" -} - -print_info() { - printf "${YELLOW}ℹ${NC} %s\n" "$1" -} - -# Build compiled executable for testing -build_executable() { - print_info "Building compiled executable..." - (cd "$CLI_DIR" && bun build --compile --minify --sourcemap \ - --external vite \ - --external @vitejs/plugin-react \ - --external @hono/vite-dev-server \ - --external @hono/vite-build \ - --external lightningcss \ - --external esbuild \ - --external rollup \ - bin/cli.ts --outfile agentuity) > /dev/null 2>&1 - if [ ! -f "$CLI_COMPILED" ]; then - print_error "Failed to build executable" - return 1 - fi - print_success "Executable built successfully" - return 0 -} - -# Test that upgrade command exists (using compiled executable) -test_upgrade_command_exists() { - print_header "Test: Upgrade Command Exists (Compiled Executable)" - - print_info "Testing compiled CLI binary..." - # Run command and capture output (may exit with error code) - "$CLI_COMPILED" upgrade --help > /tmp/upgrade-help.log 2>&1 || true - - # Verify help output contains expected text - if ! grep -q "Upgrade the CLI to the latest version" /tmp/upgrade-help.log; then - print_error "Upgrade help output missing expected text" - cat /tmp/upgrade-help.log - return 1 - fi - - print_success "Upgrade command exists and shows help" - return 0 -} - -# Test that upgrade command is hidden when run via bun -test_upgrade_rejects_bun() { - print_header "Test: Upgrade Hidden When Running via Bun" - - print_info "Testing upgrade command is hidden when run via bun..." - (cd "$CLI_DIR" && bun "$CLI_BIN" --help) > /tmp/upgrade-bun-help.log 2>&1 || true - - # Verify upgrade is NOT in the commands list - if grep -q "upgrade" /tmp/upgrade-bun-help.log; then - print_error "Upgrade command should not be visible via bun" - cat /tmp/upgrade-bun-help.log - return 1 - fi - - print_success "Upgrade correctly hidden when running via bun" - return 0 -} - -# Test upgrade JSON output schema -test_upgrade_json_schema() { - print_header "Test: Upgrade JSON Schema" - - print_info "Checking upgrade response schema..." - if ! (cd "$CLI_DIR" && bun "$CLI_BIN" ai schema show upgrade) > /tmp/upgrade-schema.log 2>&1; then - print_info "Schema command may not support upgrade yet" - print_success "Upgrade schema check skipped" - return 0 - fi - - # Verify schema contains expected fields - if grep -q "upgraded" /tmp/upgrade-schema.log && \ - grep -q "from" /tmp/upgrade-schema.log && \ - grep -q "to" /tmp/upgrade-schema.log && \ - grep -q "message" /tmp/upgrade-schema.log; then - print_success "Upgrade response schema has expected fields" - else - print_info "Schema output:" - cat /tmp/upgrade-schema.log - print_success "Upgrade schema check completed" - fi - - return 0 -} - -# Test upgrade validates --force flag -test_upgrade_force_flag() { - print_header "Test: Upgrade --force Flag" - - print_info "Testing --force flag exists..." - "$CLI_COMPILED" upgrade --help > /tmp/upgrade-force-help.log 2>&1 || true - - if grep -q -- "--force" /tmp/upgrade-force-help.log; then - print_success "Upgrade --force flag exists" - else - print_error "--force flag not found in help" - cat /tmp/upgrade-force-help.log - return 1 - fi - - return 0 -} - -# Test upgrade tags and metadata -test_upgrade_metadata() { - print_header "Test: Upgrade Command Metadata" - - print_info "Checking upgrade command metadata..." - "$CLI_COMPILED" --help > /tmp/upgrade-commands.log 2>&1 || true - - # Verify upgrade command is listed - if grep -q "upgrade" /tmp/upgrade-commands.log; then - print_success "Upgrade command listed in main help" - else - print_error "Upgrade command not in main help" - cat /tmp/upgrade-commands.log - return 1 - fi - - return 0 -} - -# Test upgrade from old version to new version (integration test) -test_upgrade_from_old_version() { - print_header "Test: Upgrade from v0.0.86 to Latest" - - tmpdir=$(mktemp -d 2>/dev/null || mktemp -d -t tmp) - cleanup() { rm -rf "$tmpdir" 2>/dev/null || true; } - trap cleanup EXIT - - # Install specific old version (0.0.86 - known to not have upgrade command) - print_info "Installing v0.0.86..." - if ! HOME="$tmpdir" VERSION=0.0.86 CI=true "$INSTALL_SCRIPT" -y > "$tmpdir/install-old.log" 2>&1; then - print_error "Failed to install v0.0.86" - cat "$tmpdir/install-old.log" - return 1 - fi - - # Verify old version is installed - old_version=$(PATH="$tmpdir/.agentuity/bin:$PATH" agentuity --version 2>&1 || echo "unknown") - print_info "Installed version: $old_version" - - if [ "$old_version" != "0.0.86" ]; then - print_error "Expected version 0.0.86, got: $old_version" - return 1 - fi - - # Verify old version doesn't have upgrade command - if PATH="$tmpdir/.agentuity/bin:$PATH" agentuity upgrade --help > "$tmpdir/old-upgrade-check.log" 2>&1; then - print_info "Old version unexpectedly has upgrade command (that's ok, it means upgrade was added earlier)" - else - print_info "Confirmed: v0.0.86 doesn't have upgrade command (expected)" - fi - - # Check what the latest version is - latest_version=$( (curl -s https://agentuity.sh/release/sdk/version 2>/dev/null | tr -d 'v') || echo "unknown") - print_info "Latest available version: $latest_version" - - # Now install latest version (should upgrade from 0.0.86 if a newer version exists) - print_info "Upgrading to latest version..." - if ! HOME="$tmpdir" CI=true "$INSTALL_SCRIPT" -y --force > "$tmpdir/install-latest.log" 2>&1; then - print_error "Failed to upgrade to latest" - cat "$tmpdir/install-latest.log" - return 1 - fi - - # Verify new version is installed - new_version=$(PATH="$tmpdir/.agentuity/bin:$PATH" agentuity --version 2>&1 || echo "unknown") - print_info "New version: $new_version" - - # Check if we actually upgraded (only if a newer version is available) - if [ "$new_version" = "$old_version" ]; then - if [ "$latest_version" = "$old_version" ] || [ "$latest_version" = "unknown" ]; then - print_success "No newer version available - already on latest ($old_version)" - else - print_error "Version did not change after upgrade (expected $latest_version, got $new_version)" - return 1 - fi - return 0 - fi - - # Verify new version has upgrade command (check help text, ignore exit code) - PATH="$tmpdir/.agentuity/bin:$PATH" agentuity upgrade --help > "$tmpdir/new-upgrade-check.log" 2>&1 || true - if ! grep -q "Upgrade the CLI to the latest version" "$tmpdir/new-upgrade-check.log"; then - print_error "New version doesn't have upgrade command" - cat "$tmpdir/new-upgrade-check.log" - return 1 - fi - - print_success "Successfully upgraded from v$old_version to v$new_version" - - # Test the actual upgrade command (with --force since we're already on latest) - print_info "Testing upgrade command execution..." - PATH="$tmpdir/.agentuity/bin:$PATH" agentuity upgrade --json > "$tmpdir/upgrade-exec.log" 2>&1 - exit_status=$? - - print_info "Upgrade command output:" - cat "$tmpdir/upgrade-exec.log" | head -20 - - if [ $exit_status -eq 0 ]; then - print_success "Upgrade command executed successfully" - else - print_info "Upgrade command exited with status $exit_status (network issues are tolerated)" - fi - - cleanup - return 0 -} - -# Run all tests -main() { - print_header "Upgrade Command Test Suite" - - # Build executable first - if ! build_executable; then - print_error "Failed to build executable - cannot run tests" - return 1 - fi - - failed=0 - total=0 - - # Test 1: Upgrade command exists - total=$((total + 1)) - if ! test_upgrade_command_exists; then - print_error "Test 1 failed: Upgrade command exists" - failed=$((failed + 1)) - fi - - # Test 2: Upgrade hidden when running via bun - total=$((total + 1)) - if ! test_upgrade_rejects_bun; then - print_error "Test 2 failed: Upgrade hidden when running via bun" - failed=$((failed + 1)) - fi - - # Test 3: Upgrade JSON schema - total=$((total + 1)) - if ! test_upgrade_json_schema; then - print_error "Test 3 failed: Upgrade JSON schema" - failed=$((failed + 1)) - fi - - # Test 4: Upgrade --force flag - total=$((total + 1)) - if ! test_upgrade_force_flag; then - print_error "Test 4 failed: Upgrade --force flag" - failed=$((failed + 1)) - fi - - # Test 5: Upgrade metadata - total=$((total + 1)) - if ! test_upgrade_metadata; then - print_error "Test 5 failed: Upgrade metadata" - failed=$((failed + 1)) - fi - - # Test 6: Upgrade from old version (integration test) - total=$((total + 1)) - if ! test_upgrade_from_old_version; then - print_error "Test 6 failed: Upgrade from old version" - failed=$((failed + 1)) - fi - - # Summary - print_header "Test Summary" - printf "Total: %d\n" "$total" - printf "Passed: %d\n" "$((total - failed))" - printf "Failed: %d\n" "$failed" - - # Cleanup compiled executable - if [ -f "$CLI_COMPILED" ]; then - rm -f "$CLI_COMPILED" - print_info "Cleaned up compiled executable" - fi - - if [ "$failed" -eq 0 ]; then - print_success "All upgrade tests passed!" - return 0 - else - print_error "Some upgrade tests failed" - return 1 - fi -} - -main "$@" From 45442bc2ca4df0f0ed72bf4496a6b2a4ae96715d Mon Sep 17 00:00:00 2001 From: Jeff Haynie Date: Thu, 22 Jan 2026 08:22:54 -0600 Subject: [PATCH 02/11] feedback --- install.sh | 9 ++++++++- packages/cli/src/cmd/canary/index.ts | 20 +++++++++++++++++--- packages/cli/src/cmd/setup/index.ts | 8 +++++++- packages/cli/src/utils/installation-type.ts | 3 ++- 4 files changed, 34 insertions(+), 6 deletions(-) diff --git a/install.sh b/install.sh index f192f3919..40b4d17f6 100755 --- a/install.sh +++ b/install.sh @@ -206,6 +206,9 @@ check_legacy_binary() { install_cli() { print_message debug "Installing Agentuity CLI..." + # Temporarily disable set -e for bun add to ensure proper error handling + set +e + if [ -n "$requested_version" ]; then # Normalize version (remove 'v' prefix if present) version=$(echo "$requested_version" | sed 's/^v//') @@ -216,6 +219,7 @@ install_cli() { install_result=$? if [ $install_result -ne 0 ]; then + set -e print_message error "Failed to install @agentuity/cli@$version" printf "%s\n" "$install_output" exit 1 @@ -232,6 +236,7 @@ install_cli() { install_result=$? if [ $install_result -ne 0 ]; then + set -e print_message error "Failed to install @agentuity/cli" printf "%s\n" "$install_output" exit 1 @@ -241,6 +246,9 @@ install_cli() { printf "%s\n" "$install_output" fi fi + + # Re-enable set -e + set -e print_message debug "Agentuity CLI installed successfully!" } @@ -249,7 +257,6 @@ install_cli() { create_legacy_shim() { legacy_dir="$HOME/.agentuity/bin" legacy_bin="$legacy_dir/agentuity" - bun_bin="$HOME/.bun/bin/agentuity" # Only create shim if ~/.agentuity/bin exists or was previously used if [ -d "$legacy_dir" ] || [ -f "$legacy_bin" ]; then diff --git a/packages/cli/src/cmd/canary/index.ts b/packages/cli/src/cmd/canary/index.ts index b9745fa93..d93169a1c 100644 --- a/packages/cli/src/cmd/canary/index.ts +++ b/packages/cli/src/cmd/canary/index.ts @@ -17,8 +17,12 @@ const CanaryResponseSchema = z.object({ message: z.string().describe('Status message'), }); -function isUrl(str: string): boolean { - return str.startsWith('http://') || str.startsWith('https://'); +function isHttpsUrl(str: string): boolean { + return str.startsWith('https://'); +} + +function isHttpUrl(str: string): boolean { + return str.startsWith('http://') && !str.startsWith('https://'); } export const command = createCommand({ @@ -57,10 +61,20 @@ export const command = createCommand({ const targetIndex = canaryIndex >= 0 ? argv.indexOf(target, canaryIndex) : -1; const forwardArgs = targetIndex >= 0 ? argv.slice(targetIndex + 1) : args.args.slice(1); + // Reject HTTP URLs for security + if (isHttpUrl(target)) { + tui.error('HTTP URLs are not allowed. Please use HTTPS.'); + return { + executed: false, + version: '', + message: 'HTTP URLs are not allowed for security reasons', + }; + } + let version: string; let tarballUrl: string; - if (isUrl(target)) { + if (isHttpsUrl(target)) { // Direct URL to tarball tarballUrl = target; // Extract version from URL if possible diff --git a/packages/cli/src/cmd/setup/index.ts b/packages/cli/src/cmd/setup/index.ts index 58b7c8d4a..d2a72a752 100644 --- a/packages/cli/src/cmd/setup/index.ts +++ b/packages/cli/src/cmd/setup/index.ts @@ -38,7 +38,13 @@ export const command = createCommand({ clearOnSuccess: true, callback: async () => { // Re-run the CLI with 'login' instead of 'setup' - const cmd = process.argv.map((x) => (x === 'setup' ? 'login' : x)); + // Only replace the first occurrence of 'setup' to avoid replacing user data + const argv = process.argv; + const setupIndex = argv.indexOf('setup'); + const cmd = + setupIndex >= 0 + ? [...argv.slice(0, setupIndex), 'login', ...argv.slice(setupIndex + 1)] + : argv; const r = Bun.spawn({ cmd: cmd.concat('--json'), stdout: 'pipe', diff --git a/packages/cli/src/utils/installation-type.ts b/packages/cli/src/utils/installation-type.ts index 98765af2f..b6bcc1540 100644 --- a/packages/cli/src/utils/installation-type.ts +++ b/packages/cli/src/utils/installation-type.ts @@ -12,7 +12,8 @@ export type InstallationType = 'global' | 'local' | 'source'; * @returns 'source' - Running from source code (development) */ export function getInstallationType(): InstallationType { - const mainPath = Bun.main; + // Normalize path to POSIX separators for cross-platform compatibility + const mainPath = Bun.main.replace(/\\/g, '/'); // Global install: ~/.bun/install/global/node_modules/@agentuity/cli/... if (mainPath.includes('/.bun/install/global/')) { From bfe8989cc6fb0ff2e3f316b3f6362f306a320a25 Mon Sep 17 00:00:00 2001 From: Jeff Haynie Date: Thu, 22 Jan 2026 08:29:31 -0600 Subject: [PATCH 03/11] fix: add Bun auto-install/upgrade to install.sh and fix CI tests - install.sh now auto-installs Bun in CI/non-interactive mode - install.sh prompts user to install/upgrade Bun in interactive mode - Fix 'Upgrade command available' test to check main help for upgrade command - Fix test-bun-check.sh to look for agentuity at ~/.bun/bin/ not ~/.agentuity/bin/ --- .github/workflows/test-install.yaml | 9 ++- install.sh | 117 ++++++++++++++++++++++++---- scripts/test-bun-check.sh | 8 +- 3 files changed, 115 insertions(+), 19 deletions(-) diff --git a/.github/workflows/test-install.yaml b/.github/workflows/test-install.yaml index 9470445d2..ccda83b5f 100644 --- a/.github/workflows/test-install.yaml +++ b/.github/workflows/test-install.yaml @@ -252,13 +252,18 @@ jobs: # Install CLI ./install.sh -y > /dev/null 2>&1 - # Check upgrade command exists - if ! $HOME/.bun/bin/agentuity upgrade --help > /tmp/upgrade-help.log 2>&1; then + # Check upgrade command exists by looking for it in --help output + $HOME/.bun/bin/agentuity --help > /tmp/help.log 2>&1 || true + + if ! grep -q "upgrade" /tmp/help.log; then echo "⚠ Upgrade command not found (expected until new version is published)" echo "✅ Test passed (pre-release state)" exit 0 fi + # Verify upgrade --help works + $HOME/.bun/bin/agentuity upgrade --help > /tmp/upgrade-help.log 2>&1 || true + if ! grep -q "Upgrade the CLI to the latest version" /tmp/upgrade-help.log; then echo "❌ Upgrade help missing expected text" cat /tmp/upgrade-help.log diff --git a/install.sh b/install.sh index 40b4d17f6..494b32d36 100755 --- a/install.sh +++ b/install.sh @@ -130,30 +130,121 @@ version_gte() { return 1 } +# Install Bun +install_bun() { + print_message info "${MUTED}Installing Bun...${NC}" + + # Temporarily disable set -e for bun install + set +e + install_output=$(curl -fsSL https://bun.sh/install | bash 2>&1) + install_result=$? + set -e + + if [ $install_result -ne 0 ]; then + print_message error "Failed to install Bun" + printf "%s\n" "$install_output" + exit 1 + fi + + if [ "$verbose" = "true" ]; then + printf "%s\n" "$install_output" + fi + + # Add Bun to PATH for current session + export PATH="$HOME/.bun/bin:$PATH" + + print_message success "Bun installed successfully" +} + +# Upgrade Bun +upgrade_bun() { + print_message info "${MUTED}Upgrading Bun...${NC}" + + # Temporarily disable set -e for bun upgrade + set +e + upgrade_output=$(bun upgrade 2>&1) + upgrade_result=$? + set -e + + if [ $upgrade_result -ne 0 ]; then + print_message error "Failed to upgrade Bun" + printf "%s\n" "$upgrade_output" + exit 1 + fi + + if [ "$verbose" = "true" ]; then + printf "%s\n" "$upgrade_output" + fi + + print_message success "Bun upgraded successfully" +} + # Check if Bun is installed and meets minimum version check_bun() { ensure_bun_on_path if ! command -v bun >/dev/null 2>&1; then - print_message error "Bun is required but not installed." - printf "\n" - print_message info "Install Bun first by running:" - printf "\n" - print_message info " ${CYAN}curl -fsSL https://bun.sh/install | bash${NC}" - printf "\n" - print_message info "Then run this installer again." + print_message warning "Bun is required but not installed." + + if [ "$non_interactive" = true ]; then + # In CI/non-interactive mode, auto-install Bun + install_bun + else + # Prompt user to install Bun + printf "Do you want to install Bun? (y/N): " + read -r response /dev/null || read -r response + case "$response" in + [yY][eE][sS] | [yY]) + install_bun + ;; + *) + print_message error "Bun is required. Please install it manually:" + print_message info " ${CYAN}curl -fsSL https://bun.sh/install | bash${NC}" + exit 1 + ;; + esac + fi + fi + + # Re-check that bun is available after potential install + ensure_bun_on_path + + if ! command -v bun >/dev/null 2>&1; then + print_message error "Bun installation failed or not found on PATH" exit 1 fi bun_version=$(bun --version 2>/dev/null || echo "0.0.0") if ! version_gte "$bun_version" "$MIN_BUN_VERSION"; then - print_message error "Bun version $bun_version is too old. Minimum required: $MIN_BUN_VERSION" - printf "\n" - print_message info "Update Bun by running:" - printf "\n" - print_message info " ${CYAN}bun upgrade${NC}" - exit 1 + print_message warning "Bun version $bun_version is too old. Minimum required: $MIN_BUN_VERSION" + + if [ "$non_interactive" = true ]; then + # In CI/non-interactive mode, auto-upgrade Bun + upgrade_bun + bun_version=$(bun --version 2>/dev/null || echo "0.0.0") + else + # Prompt user to upgrade Bun + printf "Do you want to upgrade Bun? (y/N): " + read -r response /dev/null || read -r response + case "$response" in + [yY][eE][sS] | [yY]) + upgrade_bun + bun_version=$(bun --version 2>/dev/null || echo "0.0.0") + ;; + *) + print_message error "Bun $MIN_BUN_VERSION or higher is required. Please upgrade manually:" + print_message info " ${CYAN}bun upgrade${NC}" + exit 1 + ;; + esac + fi + + # Verify upgrade was successful + if ! version_gte "$bun_version" "$MIN_BUN_VERSION"; then + print_message error "Bun upgrade failed. Current version: $bun_version, required: $MIN_BUN_VERSION" + exit 1 + fi fi print_message debug "Using Bun version $bun_version" diff --git a/scripts/test-bun-check.sh b/scripts/test-bun-check.sh index 43b18fdbd..dff872747 100755 --- a/scripts/test-bun-check.sh +++ b/scripts/test-bun-check.sh @@ -68,8 +68,8 @@ test_no_bun_ci_mode() { fi print_info "Verifying agentuity was installed..." - if ! docker exec "$container_name" test -f /root/.agentuity/bin/agentuity; then - print_error "Agentuity binary not found" + if ! docker exec "$container_name" test -f /root/.bun/bin/agentuity; then + print_error "Agentuity binary not found at /root/.bun/bin/agentuity" docker rm -f "$container_name" >/dev/null 2>&1 return 1 fi @@ -109,8 +109,8 @@ test_bun_install_prompt() { fi print_info "Verifying agentuity was installed..." - if ! docker exec "$container_name" test -f /root/.agentuity/bin/agentuity; then - print_error "Agentuity binary not found - installation did not continue after Bun install" + if ! docker exec "$container_name" test -f /root/.bun/bin/agentuity; then + print_error "Agentuity binary not found at /root/.bun/bin/agentuity" docker rm -f "$container_name" >/dev/null 2>&1 return 1 fi From 8d957e7c5a42d1da637df1e1f8edc48032f9f00a Mon Sep 17 00:00:00 2001 From: Jeff Haynie Date: Thu, 22 Jan 2026 08:52:12 -0600 Subject: [PATCH 04/11] fix: handle ensure_bun_on_path return value with set -e The ensure_bun_on_path function returns 1 when bun is not found, which with set -e causes immediate script exit. Use || true to prevent early termination and let check_bun handle the case properly. --- install.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/install.sh b/install.sh index 494b32d36..c5e20a743 100755 --- a/install.sh +++ b/install.sh @@ -181,7 +181,8 @@ upgrade_bun() { # Check if Bun is installed and meets minimum version check_bun() { - ensure_bun_on_path + # Try to add bun to PATH if it exists (ignore return value) + ensure_bun_on_path || true if ! command -v bun >/dev/null 2>&1; then print_message warning "Bun is required but not installed." @@ -207,7 +208,7 @@ check_bun() { fi # Re-check that bun is available after potential install - ensure_bun_on_path + ensure_bun_on_path || true if ! command -v bun >/dev/null 2>&1; then print_message error "Bun installation failed or not found on PATH" From 49962415a3eeea370db73bcaca495a21401d9525 Mon Sep 17 00:00:00 2001 From: Jeff Haynie Date: Thu, 22 Jan 2026 09:22:19 -0600 Subject: [PATCH 05/11] refactor: use BUN_BIN_DIR variable for custom bun install paths Define BUN_BIN_DIR=${BUN_INSTALL:-$HOME/.bun}/bin at the top and use it everywhere instead of hardcoding $HOME/.bun/bin. This supports setups using custom BUN_INSTALL locations. --- install.sh | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/install.sh b/install.sh index c5e20a743..e5d526b25 100755 --- a/install.sh +++ b/install.sh @@ -11,6 +11,7 @@ NC='\033[0m' # No Color MIN_BUN_VERSION="1.3.3" SETUP_TOKEN="-" +BUN_BIN_DIR="${BUN_INSTALL:-$HOME/.bun}/bin" requested_version=${VERSION:-} force_install=false @@ -105,8 +106,8 @@ ensure_bun_on_path() { return 0 fi - if [ -f "$HOME/.bun/bin/bun" ]; then - export PATH="$HOME/.bun/bin:$PATH" + if [ -f "$BUN_BIN_DIR/bun" ]; then + export PATH="$BUN_BIN_DIR:$PATH" return 0 fi @@ -151,7 +152,7 @@ install_bun() { fi # Add Bun to PATH for current session - export PATH="$HOME/.bun/bin:$PATH" + export PATH="$BUN_BIN_DIR:$PATH" print_message success "Bun installed successfully" } @@ -355,9 +356,9 @@ create_legacy_shim() { mkdir -p "$legacy_dir" # Create a shim script that forwards to the bun-installed version - cat > "$legacy_bin" << 'EOF' + cat > "$legacy_bin" << EOF #!/bin/sh -exec "$HOME/.bun/bin/agentuity" "$@" +exec "$BUN_BIN_DIR/agentuity" "\$@" EOF chmod 755 "$legacy_bin" print_message debug "Created compatibility shim at $legacy_bin" @@ -385,7 +386,7 @@ add_to_path() { } configure_path() { - bun_bin_dir="$HOME/.bun/bin" + bun_bin_dir="$BUN_BIN_DIR" # Check if bun bin is already on PATH case ":$PATH:" in @@ -458,8 +459,8 @@ configure_path() { # GitHub Actions PATH setup setup_github_actions() { if [ -n "${GITHUB_ACTIONS-}" ] && [ "${GITHUB_ACTIONS}" = "true" ]; then - printf "%s\n" "$HOME/.bun/bin" >>"$GITHUB_PATH" - print_message info "Added $HOME/.bun/bin to \$GITHUB_PATH" + printf "%s\n" "$BUN_BIN_DIR" >>"$GITHUB_PATH" + print_message info "Added $BUN_BIN_DIR to \$GITHUB_PATH" fi } @@ -479,7 +480,7 @@ show_path_reminder() { } run_setup() { - agentuity_bin="$HOME/.bun/bin/agentuity" + agentuity_bin="$BUN_BIN_DIR/agentuity" if [ -x "$agentuity_bin" ]; then if [ "$non_interactive" = true ]; then From 1d1dae8f59878267664196703ff8818f374377dd Mon Sep 17 00:00:00 2001 From: Jeff Haynie Date: Thu, 22 Jan 2026 09:30:16 -0600 Subject: [PATCH 06/11] fix: show_path_reminder displays correct source command for shell - show_path_reminder now accepts the config file path as parameter - configure_path sets modified_config_file for use by show_path_reminder - Displays the actual config file that was modified instead of hardcoded ~/.bashrc --- install.sh | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/install.sh b/install.sh index e5d526b25..c3a6d5e65 100755 --- a/install.sh +++ b/install.sh @@ -387,6 +387,7 @@ add_to_path() { configure_path() { bun_bin_dir="$BUN_BIN_DIR" + modified_config_file="" # Check if bun bin is already on PATH case ":$PATH:" in @@ -454,6 +455,9 @@ configure_path() { add_to_path "$config_file" "export PATH=$bun_bin_dir:\$PATH" "bun" ;; esac + + # Export the config file that was modified for show_path_reminder + modified_config_file="$config_file" } # GitHub Actions PATH setup @@ -465,7 +469,16 @@ setup_github_actions() { } show_path_reminder() { - if [ "$path_modified" = true ]; then + _config_file="${1:-}" + if [ "$path_modified" = true ] && [ -n "$_config_file" ]; then + # Determine the correct source command based on shell + _source_cmd="source $_config_file" + case "$_config_file" in + *.fish) + _source_cmd="source $_config_file" + ;; + esac + printf "\n" printf "${RED}╭────────────────────────────────────────────────────╮${NC}\n" printf "${RED}│${NC} ${RED}⚠ ACTION REQUIRED${NC} ${RED}│${NC}\n" @@ -474,7 +487,7 @@ show_path_reminder() { printf "${RED}│${NC} ${RED}│${NC}\n" printf "${RED}│ Please restart your terminal or run: │${NC}\n" printf "${RED}│${NC} ${RED}│${NC}\n" - printf "${RED}│${NC} ${CYAN}source ~/.bashrc${NC} ${RED}│${NC}\n" + printf "${RED}│${NC} ${CYAN}%s${NC}\n" "$_source_cmd" printf "${RED}╰────────────────────────────────────────────────────╯${NC}\n" fi } @@ -565,8 +578,8 @@ main() { # Clear progress indicator clear_progress - # Show PATH reminder if needed - show_path_reminder + # Show PATH reminder if needed (pass the config file that was modified) + show_path_reminder "$modified_config_file" # Run setup run_setup From d026a4444d83725823e57b3ef43337e1cc8855fe Mon Sep 17 00:00:00 2001 From: Jeff Haynie Date: Thu, 22 Jan 2026 09:48:53 -0600 Subject: [PATCH 07/11] fix: align path reminder source command line with box borders Add fixed-width padding (%-50s) and closing border to the source command line so it matches the surrounding box formatting. --- install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install.sh b/install.sh index c3a6d5e65..0685c7c13 100755 --- a/install.sh +++ b/install.sh @@ -487,7 +487,7 @@ show_path_reminder() { printf "${RED}│${NC} ${RED}│${NC}\n" printf "${RED}│ Please restart your terminal or run: │${NC}\n" printf "${RED}│${NC} ${RED}│${NC}\n" - printf "${RED}│${NC} ${CYAN}%s${NC}\n" "$_source_cmd" + printf "${RED}│${NC} ${CYAN}%-50s${NC} ${RED}│${NC}\n" "$_source_cmd" printf "${RED}╰────────────────────────────────────────────────────╯${NC}\n" fi } From 1d055225a79a9239dd841d576b662f0bb658d679 Mon Sep 17 00:00:00 2001 From: Jeff Haynie Date: Thu, 22 Jan 2026 12:11:26 -0600 Subject: [PATCH 08/11] fix: respect BUN_INSTALL_BIN and use POSIX dot notation - BUN_BIN_DIR now checks BUN_INSTALL_BIN first, then BUN_INSTALL/bin, then ~/.bun/bin - show_path_reminder uses POSIX '. file' for sh-compatible shells - Keep 'source file' only for fish shell configs --- install.sh | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/install.sh b/install.sh index 0685c7c13..888e05a33 100755 --- a/install.sh +++ b/install.sh @@ -11,7 +11,8 @@ NC='\033[0m' # No Color MIN_BUN_VERSION="1.3.3" SETUP_TOKEN="-" -BUN_BIN_DIR="${BUN_INSTALL:-$HOME/.bun}/bin" +# Respect BUN_INSTALL_BIN for custom global bin dir, fall back to BUN_INSTALL/bin, then ~/.bun/bin +BUN_BIN_DIR="${BUN_INSTALL_BIN:-${BUN_INSTALL:-$HOME/.bun}/bin}" requested_version=${VERSION:-} force_install=false @@ -471,12 +472,16 @@ setup_github_actions() { show_path_reminder() { _config_file="${1:-}" if [ "$path_modified" = true ] && [ -n "$_config_file" ]; then - # Determine the correct source command based on shell - _source_cmd="source $_config_file" + # Determine the correct source command based on shell config file + # Use POSIX ". file" for sh-compatible shells, "source file" for fish case "$_config_file" in *.fish) _source_cmd="source $_config_file" ;; + *) + # POSIX-compatible dot notation for .profile, .bashrc, .zshrc, .ashrc, etc. + _source_cmd=". $_config_file" + ;; esac printf "\n" From 92b70845d700b931cd7be97343d149264bc59285 Mon Sep 17 00:00:00 2001 From: Jeff Haynie Date: Thu, 22 Jan 2026 12:25:13 -0600 Subject: [PATCH 09/11] fix: separate bun executable dir from global package bin dir - BUN_EXEC_DIR: where bun executable lives (used by ensure_bun_on_path, install_bun) - BUN_INSTALL_BIN: where globally installed packages go (used for agentuity binary, PATH config, GitHub Actions, legacy shim) - ash/sh config_files: removed /etc/profile, prefer user-level files only - Loop now checks writability before selecting config file --- install.sh | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/install.sh b/install.sh index 888e05a33..940a1750c 100755 --- a/install.sh +++ b/install.sh @@ -11,8 +11,10 @@ NC='\033[0m' # No Color MIN_BUN_VERSION="1.3.3" SETUP_TOKEN="-" -# Respect BUN_INSTALL_BIN for custom global bin dir, fall back to BUN_INSTALL/bin, then ~/.bun/bin -BUN_BIN_DIR="${BUN_INSTALL_BIN:-${BUN_INSTALL:-$HOME/.bun}/bin}" +# BUN_EXEC_DIR: where the bun executable itself lives +BUN_EXEC_DIR="${BUN_INSTALL:-$HOME/.bun}/bin" +# BUN_INSTALL_BIN: where globally installed packages go (defaults to same as BUN_EXEC_DIR) +BUN_INSTALL_BIN="${BUN_INSTALL_BIN:-$BUN_EXEC_DIR}" requested_version=${VERSION:-} force_install=false @@ -107,8 +109,8 @@ ensure_bun_on_path() { return 0 fi - if [ -f "$BUN_BIN_DIR/bun" ]; then - export PATH="$BUN_BIN_DIR:$PATH" + if [ -f "$BUN_EXEC_DIR/bun" ]; then + export PATH="$BUN_EXEC_DIR:$PATH" return 0 fi @@ -153,7 +155,7 @@ install_bun() { fi # Add Bun to PATH for current session - export PATH="$BUN_BIN_DIR:$PATH" + export PATH="$BUN_EXEC_DIR:$PATH" print_message success "Bun installed successfully" } @@ -359,7 +361,7 @@ create_legacy_shim() { # Create a shim script that forwards to the bun-installed version cat > "$legacy_bin" << EOF #!/bin/sh -exec "$BUN_BIN_DIR/agentuity" "\$@" +exec "$BUN_INSTALL_BIN/agentuity" "\$@" EOF chmod 755 "$legacy_bin" print_message debug "Created compatibility shim at $legacy_bin" @@ -387,7 +389,7 @@ add_to_path() { } configure_path() { - bun_bin_dir="$BUN_BIN_DIR" + bun_bin_dir="$BUN_INSTALL_BIN" modified_config_file="" # Check if bun bin is already on PATH @@ -412,7 +414,8 @@ configure_path() { config_files="$HOME/.bashrc $HOME/.bash_profile $HOME/.profile $XDG_CONFIG_HOME/bash/.bashrc $XDG_CONFIG_HOME/bash/.bash_profile" ;; ash | sh) - config_files="$HOME/.ashrc $HOME/.profile /etc/profile" + # Only user-level files, avoid system-wide /etc/profile + config_files="$HOME/.profile $HOME/.ashrc" ;; *) config_files="$HOME/.bashrc $HOME/.bash_profile $XDG_CONFIG_HOME/bash/.bashrc $XDG_CONFIG_HOME/bash/.bash_profile" @@ -421,7 +424,8 @@ configure_path() { config_file="" for file in $config_files; do - if [ -f "$file" ]; then + # Only select files that exist and are writable + if [ -f "$file" ] && [ -w "$file" ]; then config_file=$file break fi @@ -464,8 +468,8 @@ configure_path() { # GitHub Actions PATH setup setup_github_actions() { if [ -n "${GITHUB_ACTIONS-}" ] && [ "${GITHUB_ACTIONS}" = "true" ]; then - printf "%s\n" "$BUN_BIN_DIR" >>"$GITHUB_PATH" - print_message info "Added $BUN_BIN_DIR to \$GITHUB_PATH" + printf "%s\n" "$BUN_INSTALL_BIN" >>"$GITHUB_PATH" + print_message info "Added $BUN_INSTALL_BIN to \$GITHUB_PATH" fi } @@ -498,7 +502,7 @@ show_path_reminder() { } run_setup() { - agentuity_bin="$BUN_BIN_DIR/agentuity" + agentuity_bin="$BUN_INSTALL_BIN/agentuity" if [ -x "$agentuity_bin" ]; then if [ "$non_interactive" = true ]; then From 9e77d5f92c7355b52d53199ef78a75d1331e05b6 Mon Sep 17 00:00:00 2001 From: Jeff Haynie Date: Thu, 22 Jan 2026 12:32:26 -0600 Subject: [PATCH 10/11] fix doc references --- apps/docs/src/agent/chat/agent.ts | 2 +- apps/docs/src/agent/context/agent.ts | 2 +- apps/docs/src/agent/evals/agent.ts | 2 +- apps/docs/src/agent/hello/agent.ts | 2 +- apps/docs/src/agent/kv/agent.ts | 2 +- apps/docs/src/agent/model-arena/agent.ts | 2 +- apps/docs/src/agent/objectstore/agent.ts | 2 +- apps/docs/src/agent/sse-stream/agent.ts | 2 +- apps/docs/src/agent/vector/agent.ts | 2 +- apps/docs/src/agent/websocket/agent.ts | 2 +- apps/docs/src/web/App.tsx | 28 +++++++++---------- apps/testing/auth-package-app/README.md | 2 +- packages/cli/docs/api-errors.md | 4 +-- packages/cli/src/api-errors.md | 4 +-- packages/cli/src/banner.ts | 4 +-- packages/cli/src/cmd/auth/signup.ts | 2 +- packages/cli/src/legacy-check.ts | 4 +-- packages/server/src/api/api.ts | 2 +- packages/vscode/src/extension.ts | 2 +- .../src/features/chat/agentuityParticipant.ts | 4 +-- 20 files changed, 37 insertions(+), 39 deletions(-) diff --git a/apps/docs/src/agent/chat/agent.ts b/apps/docs/src/agent/chat/agent.ts index 11fbc871e..9650b14f0 100644 --- a/apps/docs/src/agent/chat/agent.ts +++ b/apps/docs/src/agent/chat/agent.ts @@ -13,7 +13,7 @@ * This agent uses push() with maxRecords for automatic sliding window behavior, * keeping only the last MAX_MESSAGES to prevent unbounded growth. * - * Docs: https://preview.agentuity.dev/v1/Learn/Cookbook/Patterns/chat-with-history + * Docs: https://agentuity.dev/Learn/Cookbook/Patterns/chat-with-history */ import { createAgent } from '@agentuity/runtime'; diff --git a/apps/docs/src/agent/context/agent.ts b/apps/docs/src/agent/context/agent.ts index e7a482fb5..e3064bba5 100644 --- a/apps/docs/src/agent/context/agent.ts +++ b/apps/docs/src/agent/context/agent.ts @@ -12,7 +12,7 @@ * - ctx.kv / ctx.vector / ctx.objectstore - Storage services * - ctx.logger - Structured logging * - * Docs: https://preview.agentuity.dev/v1/Reference/sdk-reference#context-api + * Docs: https://agentuity.dev/Reference/sdk-reference#context-api */ import { createAgent, getAgents } from '@agentuity/runtime'; import { s } from '@agentuity/schema'; diff --git a/apps/docs/src/agent/evals/agent.ts b/apps/docs/src/agent/evals/agent.ts index f7b08ea3e..52d29860e 100644 --- a/apps/docs/src/agent/evals/agent.ts +++ b/apps/docs/src/agent/evals/agent.ts @@ -16,7 +16,7 @@ * * See eval.ts for the evaluation definitions. * - * Docs: https://preview.agentuity.dev/v1/Build/Agents/evaluations + * Docs: https://agentuity.dev/Agents/evaluations */ import { createAgent } from '@agentuity/runtime'; import { s } from '@agentuity/schema'; diff --git a/apps/docs/src/agent/hello/agent.ts b/apps/docs/src/agent/hello/agent.ts index d4e630913..c18014793 100644 --- a/apps/docs/src/agent/hello/agent.ts +++ b/apps/docs/src/agent/hello/agent.ts @@ -10,7 +10,7 @@ * - schema.input/output define the contract (uses @agentuity/schema, Zod, etc.) * - handler receives typed input and returns typed output * - * Docs: https://preview.agentuity.dev/v1/Build/Agents/creating-agents + * Docs: https://agentuity.dev/Agents/creating-agents */ import { createAgent } from '@agentuity/runtime'; import { s } from '@agentuity/schema'; diff --git a/apps/docs/src/agent/kv/agent.ts b/apps/docs/src/agent/kv/agent.ts index dc0b2f6b0..aedbedec1 100644 --- a/apps/docs/src/agent/kv/agent.ts +++ b/apps/docs/src/agent/kv/agent.ts @@ -12,7 +12,7 @@ * * TTL (time-to-live) is in seconds, minimum 60s. Great for caching. * - * Docs: https://preview.agentuity.dev/v1/Build/Storage/key-value + * Docs: https://agentuity.dev/Services/Storage/key-value */ import { createAgent } from '@agentuity/runtime'; import { s } from '@agentuity/schema'; diff --git a/apps/docs/src/agent/model-arena/agent.ts b/apps/docs/src/agent/model-arena/agent.ts index a36eae77c..763bc10c2 100644 --- a/apps/docs/src/agent/model-arena/agent.ts +++ b/apps/docs/src/agent/model-arena/agent.ts @@ -13,7 +13,7 @@ * The judge uses generateObject() with a Zod schema for type-safe structured * output - the model is forced to return valid JSON matching your schema. * - * Docs: https://preview.agentuity.dev/v1/Build/Agents/schema-libraries + * Docs: https://agentuity.dev/Agents/schema-libraries */ import { createAgent } from '@agentuity/runtime'; import { s } from '@agentuity/schema'; diff --git a/apps/docs/src/agent/objectstore/agent.ts b/apps/docs/src/agent/objectstore/agent.ts index 3d0fb30a4..9f92e341a 100644 --- a/apps/docs/src/agent/objectstore/agent.ts +++ b/apps/docs/src/agent/objectstore/agent.ts @@ -15,7 +15,7 @@ * - s3.presign(key, { expiresIn }) - Generate temporary shareable URL * - s3.list({ prefix }) - List files in a directory * - * Docs: https://preview.agentuity.dev/v1/Build/Storage/object + * Docs: https://agentuity.dev/Services/Storage/object */ import { createAgent } from '@agentuity/runtime'; import { s } from '@agentuity/schema'; diff --git a/apps/docs/src/agent/sse-stream/agent.ts b/apps/docs/src/agent/sse-stream/agent.ts index d7b9a4ad3..96be546ab 100644 --- a/apps/docs/src/agent/sse-stream/agent.ts +++ b/apps/docs/src/agent/sse-stream/agent.ts @@ -13,7 +13,7 @@ * the AI SDK. This agent exists for Workbench discovery but returns a fallback * message for direct calls since streaming requires the SSE transport. * - * Docs: https://preview.agentuity.dev/v1/Build/Routes/sse + * Docs: https://agentuity.dev/Routes/sse */ import { createAgent } from '@agentuity/runtime'; import { s } from '@agentuity/schema'; diff --git a/apps/docs/src/agent/vector/agent.ts b/apps/docs/src/agent/vector/agent.ts index a42421aa2..cbea3333f 100644 --- a/apps/docs/src/agent/vector/agent.ts +++ b/apps/docs/src/agent/vector/agent.ts @@ -14,7 +14,7 @@ * * Also available: get(), getMany(), delete() for direct key access. * - * Docs: https://preview.agentuity.dev/v1/Build/Storage/vector + * Docs: https://agentuity.dev/Services/Storage/vector */ import { createAgent } from '@agentuity/runtime'; import { s } from '@agentuity/schema'; diff --git a/apps/docs/src/agent/websocket/agent.ts b/apps/docs/src/agent/websocket/agent.ts index 0ca25c317..6a8a407ab 100644 --- a/apps/docs/src/agent/websocket/agent.ts +++ b/apps/docs/src/agent/websocket/agent.ts @@ -14,7 +14,7 @@ * The route (router.websocket) handles the connection lifecycle (onOpen, onClose). * This agent processes individual messages - it's a simple echo that adds timestamps. * - * Docs: https://preview.agentuity.dev/v1/Build/Routes/websockets + * Docs: https://agentuity.dev/Routes/websockets */ import { createAgent } from '@agentuity/runtime'; import { s } from '@agentuity/schema'; diff --git a/apps/docs/src/web/App.tsx b/apps/docs/src/web/App.tsx index 2c4f96391..5442c64bf 100644 --- a/apps/docs/src/web/App.tsx +++ b/apps/docs/src/web/App.tsx @@ -626,7 +626,7 @@ const DEMOS: DemoConfig[] = [ to see what tools are available inside your handler. ), - docsUrl: 'https://preview.agentuity.dev/v1/Build/Agents/creating-agents', + docsUrl: 'https://agentuity.dev/Agents/creating-agents', category: 'basics', component: HelloDemo, codeExample: CODE_EXAMPLES.hello, @@ -667,7 +667,7 @@ const DEMOS: DemoConfig[] = [ storage, thread state for conversations, and background task scheduling. ), - docsUrl: 'https://preview.agentuity.dev/v1/Reference/sdk-reference#context-api', + docsUrl: 'https://agentuity.dev/Reference/sdk-reference#context-api', category: 'basics', component: HandlerContextDemo, codeExample: CODE_EXAMPLES['handler-context'], @@ -697,7 +697,7 @@ const DEMOS: DemoConfig[] = [ instead. ), - docsUrl: 'https://preview.agentuity.dev/v1/Build/Storage/key-value', + docsUrl: 'https://agentuity.dev/Services/Storage/key-value', category: 'services', component: KVExplorer, codeExample: CODE_EXAMPLES['key-value'], @@ -730,7 +730,7 @@ const DEMOS: DemoConfig[] = [ instead. ), - docsUrl: 'https://preview.agentuity.dev/v1/Build/Storage/vector', + docsUrl: 'https://agentuity.dev/Services/Storage/vector', category: 'services', component: VectorSearch, codeExample: CODE_EXAMPLES['vector-storage'], @@ -759,7 +759,7 @@ const DEMOS: DemoConfig[] = [ . ), - docsUrl: 'https://preview.agentuity.dev/v1/Build/Storage/object', + docsUrl: 'https://agentuity.dev/Services/Storage/object', category: 'services', component: ObjectStoreDemo, codeExample: CODE_EXAMPLES['object-storage'], @@ -780,7 +780,7 @@ const DEMOS: DemoConfig[] = [ with the Vercel AI SDK for streaming and structured output. ), - docsUrl: 'https://preview.agentuity.dev/v1/Build/Agents/ai-gateway', + docsUrl: 'https://agentuity.dev/Agents/ai-gateway', category: 'services', component: AIGatewayDemo, codeExample: CODE_EXAMPLES['ai-gateway'], @@ -810,7 +810,7 @@ const DEMOS: DemoConfig[] = [ . ), - docsUrl: 'https://preview.agentuity.dev/v1/Build/Agents/streaming-responses', + docsUrl: 'https://agentuity.dev/Agents/streaming-responses', category: 'io-patterns', component: StreamingDemo, codeExample: CODE_EXAMPLES.streaming, @@ -838,7 +838,7 @@ const DEMOS: DemoConfig[] = [ . ), - docsUrl: 'https://preview.agentuity.dev/v1/Build/Routes/sse', + docsUrl: 'https://agentuity.dev/Routes/sse', category: 'io-patterns', component: SSEStreamDemo, codeExample: CODE_EXAMPLES['sse-stream'], @@ -867,7 +867,7 @@ const DEMOS: DemoConfig[] = [ create, list, and manage your streams. ), - docsUrl: 'https://preview.agentuity.dev/v1/Build/Storage/durable-streams', + docsUrl: 'https://agentuity.dev/Services/Storage/durable-streams', category: 'io-patterns', component: PersistentStreamDemo, codeExample: CODE_EXAMPLES['durable-stream'], @@ -889,7 +889,7 @@ const DEMOS: DemoConfig[] = [ fire-and-forget background tasks. ), - docsUrl: 'https://preview.agentuity.dev/v1/Build/Agents/calling-other-agents', + docsUrl: 'https://agentuity.dev/Agents/calling-other-agents', category: 'io-patterns', component: AgentCallsDemo, codeExample: CODE_EXAMPLES['agent-calls'], @@ -919,7 +919,7 @@ const DEMOS: DemoConfig[] = [ to cache results so you don't have to fetch them each time. ), - docsUrl: 'https://preview.agentuity.dev/v1/Build/Routes/cron', + docsUrl: 'https://agentuity.dev/Routes/cron', category: 'io-patterns', component: CronDemo, codeExample: CODE_EXAMPLES.cron, @@ -948,7 +948,7 @@ const DEMOS: DemoConfig[] = [ for more on state management. ), - docsUrl: 'https://preview.agentuity.dev/v1/Learn/Cookbook/Patterns/chat-with-history', + docsUrl: 'https://agentuity.dev/Learn/Cookbook/Patterns/chat-with-history', category: 'examples', component: ChatDemo, codeExample: CODE_EXAMPLES.chat, @@ -976,7 +976,7 @@ const DEMOS: DemoConfig[] = [ prompts. ), - docsUrl: 'https://preview.agentuity.dev/v1/Build/Agents/schema-libraries', + docsUrl: 'https://agentuity.dev/Agents/schema-libraries', category: 'examples', component: ModelArena, codeExample: CODE_EXAMPLES['model-arena'], @@ -999,7 +999,7 @@ const DEMOS: DemoConfig[] = [ over time. ), - docsUrl: 'https://preview.agentuity.dev/v1/Build/Agents/evaluations', + docsUrl: 'https://agentuity.dev/Agents/evaluations', category: 'examples', component: EvalsDemo, codeExample: CODE_EXAMPLES.evals, diff --git a/apps/testing/auth-package-app/README.md b/apps/testing/auth-package-app/README.md index 4f2a40e06..6cb7ef65e 100644 --- a/apps/testing/auth-package-app/README.md +++ b/apps/testing/auth-package-app/README.md @@ -188,6 +188,6 @@ Both methods produce the same `c.var.auth` context in routes. ## Learn More -- [Agentuity Auth Documentation](https://agentuity.dev/docs/auth) +- [Agentuity Auth Documentation](https://agentuity.dev/Frontend/authentication) - [BetterAuth Documentation](https://better-auth.com/docs) - [Agentuity SDK](https://github.com/agentuity/sdk) diff --git a/packages/cli/docs/api-errors.md b/packages/cli/docs/api-errors.md index 3043d6a98..0755f8783 100644 --- a/packages/cli/docs/api-errors.md +++ b/packages/cli/docs/api-errors.md @@ -63,7 +63,7 @@ When the CLI version is outdated or incompatible with the API, the server return { "success": false, "code": "UPGRADE_REQUIRED", - "message": "Please upgrade to the latest version of the CLI. Instructions at https://agentuity.dev/CLI/installation" + "message": "Please upgrade to the latest version of the CLI. Instructions at https://agentuity.dev/Get-Started/installation" } ``` @@ -84,7 +84,7 @@ try { if (error instanceof UpgradeRequiredError) { logger.error('⚠ CLI Upgrade Required'); logger.error(error.message); - logger.error('Visit: https://agentuity.dev/CLI/installation'); + logger.error('Visit: https://agentuity.dev/Get-Started/installation'); process.exit(1); } throw error; diff --git a/packages/cli/src/api-errors.md b/packages/cli/src/api-errors.md index 3043d6a98..0755f8783 100644 --- a/packages/cli/src/api-errors.md +++ b/packages/cli/src/api-errors.md @@ -63,7 +63,7 @@ When the CLI version is outdated or incompatible with the API, the server return { "success": false, "code": "UPGRADE_REQUIRED", - "message": "Please upgrade to the latest version of the CLI. Instructions at https://agentuity.dev/CLI/installation" + "message": "Please upgrade to the latest version of the CLI. Instructions at https://agentuity.dev/Get-Started/installation" } ``` @@ -84,7 +84,7 @@ try { if (error instanceof UpgradeRequiredError) { logger.error('⚠ CLI Upgrade Required'); logger.error(error.message); - logger.error('Visit: https://agentuity.dev/CLI/installation'); + logger.error('Visit: https://agentuity.dev/Get-Started/installation'); process.exit(1); } throw error; diff --git a/packages/cli/src/banner.ts b/packages/cli/src/banner.ts index 51b30b28b..f11c8500a 100644 --- a/packages/cli/src/banner.ts +++ b/packages/cli/src/banner.ts @@ -35,8 +35,8 @@ export function generateBanner(version?: string, compact?: true): string { const docsLabel = ' Docs: '; const docsLink = LINKS - ? link('https://preview.agentuity.dev', 'preview.agentuity.dev', WHITE!) - : WHITE + 'https://preview.agentuity.dev' + RESET; + ? link('https://agentuity.dev', 'preview.agentuity.dev', WHITE!) + : WHITE + 'https://agentuity.dev' + RESET; const docsWidth = getDisplayWidth(stripAnsi(docsLink)); const docsPadding = width - docsLabel.length - docsWidth - 1; diff --git a/packages/cli/src/cmd/auth/signup.ts b/packages/cli/src/cmd/auth/signup.ts index 1daca5264..6b80ee2e2 100644 --- a/packages/cli/src/cmd/auth/signup.ts +++ b/packages/cli/src/cmd/auth/signup.ts @@ -51,7 +51,7 @@ export const signupCommand = createSubcommand({ tui.success('Welcome to Agentuity! You are now logged in'); } catch (error) { if (error instanceof UpgradeRequiredError) { - const bannerBody = `${error.message}\n\nVisit: ${tui.link('https://preview.agentuity.dev/v1/Get-Started/installation')}`; + const bannerBody = `${error.message}\n\nVisit: ${tui.link('https://agentuity.dev/Get-Started/installation')}`; tui.banner('CLI Upgrade Required', bannerBody); process.exit(1); } else if (error instanceof Error) { diff --git a/packages/cli/src/legacy-check.ts b/packages/cli/src/legacy-check.ts index 157a1da3c..e613b7965 100644 --- a/packages/cli/src/legacy-check.ts +++ b/packages/cli/src/legacy-check.ts @@ -130,9 +130,7 @@ export async function checkLegacyCLI(): Promise { tui.bullet('curl -sSL https://v1.agentuity.sh | sh'); tui.newline(); - console.log( - ` Learn more: ${tui.link('https://preview.agentuity.dev/v1/Reference/migration-guide')}` - ); + console.log(` Learn more: ${tui.link('https://agentuity.dev/Reference/migration-guide')}`); tui.newline(); process.exit(1); diff --git a/packages/server/src/api/api.ts b/packages/server/src/api/api.ts index e95994687..20a0ecb85 100644 --- a/packages/server/src/api/api.ts +++ b/packages/server/src/api/api.ts @@ -77,7 +77,7 @@ export const ValidationOutputError = StructuredError( export const UpgradeRequiredError = StructuredError( 'UpgradeRequiredError', - 'Upgrade required to continue. Please see https://agentuity.dev/CLI/installation to download the latest version of the SDK.' + 'Upgrade required to continue. Please see https://agentuity.dev/Get-Started/installation to download the latest version of the SDK.' )<{ sessionId?: string | null; }>(); diff --git a/packages/vscode/src/extension.ts b/packages/vscode/src/extension.ts index 5876bebba..29423e577 100644 --- a/packages/vscode/src/extension.ts +++ b/packages/vscode/src/extension.ts @@ -188,7 +188,7 @@ function registerSetupCommands(context: vscode.ExtensionContext): void { context.subscriptions.push( vscode.commands.registerCommand('agentuity.installCli', () => { void vscode.env.openExternal( - vscode.Uri.parse('https://agentuity.dev/Introduction/getting-started') + vscode.Uri.parse('https://agentuity.dev/Get-Started/quickstart') ); }) ); diff --git a/packages/vscode/src/features/chat/agentuityParticipant.ts b/packages/vscode/src/features/chat/agentuityParticipant.ts index e8f58184e..fb568f260 100644 --- a/packages/vscode/src/features/chat/agentuityParticipant.ts +++ b/packages/vscode/src/features/chat/agentuityParticipant.ts @@ -72,7 +72,7 @@ async function handleChatRequest( stream.markdown('The Agentuity CLI is required. Install it with:\n\n'); stream.markdown('```bash\nbun install -g @agentuity/cli\n```\n\n'); stream.markdown( - 'Or visit the [Getting Started Guide](https://agentuity.dev/Introduction/getting-started)' + 'Or visit the [Getting Started Guide](https://agentuity.dev/Get-Started/quickstart)' ); stream.button({ title: 'Install CLI', @@ -388,7 +388,7 @@ async function handleListAgents(stream: vscode.ChatResponseStream): Promise Date: Thu, 22 Jan 2026 12:39:39 -0600 Subject: [PATCH 11/11] feedback --- packages/cli/src/banner.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/banner.ts b/packages/cli/src/banner.ts index f11c8500a..e375ed80f 100644 --- a/packages/cli/src/banner.ts +++ b/packages/cli/src/banner.ts @@ -35,7 +35,7 @@ export function generateBanner(version?: string, compact?: true): string { const docsLabel = ' Docs: '; const docsLink = LINKS - ? link('https://agentuity.dev', 'preview.agentuity.dev', WHITE!) + ? link('https://agentuity.dev', 'agentuity.dev', WHITE!) : WHITE + 'https://agentuity.dev' + RESET; const docsWidth = getDisplayWidth(stripAnsi(docsLink)); const docsPadding = width - docsLabel.length - docsWidth - 1;