mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-04-29 03:00:45 -04:00
Compare commits
5 Commits
1.0.0-cli
...
openhands/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
944530a72d | ||
|
|
09de78d426 | ||
|
|
c034cc5dfb | ||
|
|
9bd02440b0 | ||
|
|
c9d8782566 |
@@ -12,6 +12,9 @@ on:
|
||||
paths:
|
||||
- "openhands-cli/**"
|
||||
|
||||
permissions:
|
||||
contents: write # needed to create releases or upload assets
|
||||
|
||||
# Cancel previous runs if a new commit is pushed
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ (github.head_ref && github.ref) || github.run_id }}
|
||||
@@ -22,7 +25,23 @@ jobs:
|
||||
name: Build and test binary executable
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
include:
|
||||
# Linux x86_64
|
||||
- os: ubuntu-latest
|
||||
arch: x86_64
|
||||
platform: linux
|
||||
# Linux ARM64
|
||||
- os: ubuntu-latest-arm64
|
||||
arch: arm64
|
||||
platform: linux
|
||||
# macOS x86_64 (Intel)
|
||||
- os: macos-13
|
||||
arch: x86_64
|
||||
platform: macos
|
||||
# macOS ARM64 (Apple Silicon)
|
||||
- os: macos-latest
|
||||
arch: arm64
|
||||
platform: macos
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
@@ -49,7 +68,14 @@ jobs:
|
||||
- name: Build binary executable
|
||||
working-directory: openhands-cli
|
||||
run: |
|
||||
./build.sh --install-pyinstaller | tee output.log
|
||||
# Set build arguments based on platform and architecture
|
||||
BUILD_ARGS="--install-pyinstaller"
|
||||
if [ "${{ matrix.platform }}" = "macos" ]; then
|
||||
BUILD_ARGS="$BUILD_ARGS --target-arch ${{ matrix.arch }}"
|
||||
fi
|
||||
|
||||
echo "🔨 Building with args: $BUILD_ARGS"
|
||||
./build.sh $BUILD_ARGS | tee output.log
|
||||
echo "Full output:"
|
||||
cat output.log
|
||||
|
||||
@@ -64,7 +90,7 @@ jobs:
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: openhands-cli-${{ matrix.os }}
|
||||
name: openhands-cli-${{ matrix.platform }}-${{ matrix.arch }}
|
||||
path: openhands-cli/dist/openhands*
|
||||
retention-days: 30
|
||||
|
||||
@@ -85,13 +111,24 @@ jobs:
|
||||
- name: Prepare release assets
|
||||
run: |
|
||||
mkdir -p release-assets
|
||||
# Rename binaries to include OS in filename
|
||||
if [ -f artifacts/openhands-cli-ubuntu-latest/openhands ]; then
|
||||
cp artifacts/openhands-cli-ubuntu-latest/openhands release-assets/openhands-linux
|
||||
fi
|
||||
if [ -f artifacts/openhands-cli-macos-latest/openhands ]; then
|
||||
cp artifacts/openhands-cli-macos-latest/openhands release-assets/openhands-macos
|
||||
fi
|
||||
# Rename binaries to include platform and architecture in filename
|
||||
for artifact_dir in artifacts/openhands-cli-*; do
|
||||
if [ -d "$artifact_dir" ]; then
|
||||
# Extract platform and arch from directory name
|
||||
# Format: openhands-cli-{platform}-{arch}
|
||||
dir_name=$(basename "$artifact_dir")
|
||||
platform_arch=${dir_name#openhands-cli-}
|
||||
|
||||
if [ -f "$artifact_dir/openhands" ]; then
|
||||
cp "$artifact_dir/openhands" "release-assets/openhands-$platform_arch"
|
||||
echo "✅ Copied $artifact_dir/openhands to release-assets/openhands-$platform_arch"
|
||||
else
|
||||
echo "⚠️ No openhands binary found in $artifact_dir"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo "📁 Release assets prepared:"
|
||||
ls -la release-assets/
|
||||
|
||||
- name: Create GitHub Release
|
||||
@@ -101,4 +138,4 @@ jobs:
|
||||
draft: true
|
||||
prerelease: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.CLI_RELEASE_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
107
.github/workflows/cli-build-test.yml
vendored
107
.github/workflows/cli-build-test.yml
vendored
@@ -1,107 +0,0 @@
|
||||
# Workflow that builds and tests the CLI binary executable
|
||||
name: CLI - Build and Test Binary
|
||||
|
||||
# Run on pushes to main branch and CLI tags, and on pull requests when CLI files change
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- "*-cli"
|
||||
pull_request:
|
||||
paths:
|
||||
- "openhands-cli/**"
|
||||
|
||||
permissions:
|
||||
contents: write # needed to create releases or upload assets
|
||||
|
||||
# Cancel previous runs if a new commit is pushed
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ (github.head_ref && github.ref) || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build-and-test-binary:
|
||||
name: Build and test binary executable
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.12
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v3
|
||||
with:
|
||||
version: "latest"
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: openhands-cli
|
||||
run: |
|
||||
uv sync
|
||||
|
||||
- name: Build binary executable
|
||||
working-directory: openhands-cli
|
||||
run: |
|
||||
./build.sh --install-pyinstaller | tee output.log
|
||||
echo "Full output:"
|
||||
cat output.log
|
||||
|
||||
if grep -q "❌" output.log; then
|
||||
echo "❌ Found failure marker in output"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Build & test finished without ❌ markers"
|
||||
|
||||
- name: Upload binary artifact (for releases only)
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: openhands-cli-${{ matrix.os }}
|
||||
path: openhands-cli/dist/openhands*
|
||||
retention-days: 30
|
||||
|
||||
create-github-release:
|
||||
name: Create GitHub Release
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-and-test-binary
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
- name: Prepare release assets
|
||||
run: |
|
||||
mkdir -p release-assets
|
||||
# Rename binaries to include OS in filename
|
||||
if [ -f artifacts/openhands-cli-ubuntu-latest/openhands ]; then
|
||||
cp artifacts/openhands-cli-ubuntu-latest/openhands release-assets/openhands-linux
|
||||
fi
|
||||
if [ -f artifacts/openhands-cli-macos-latest/openhands ]; then
|
||||
cp artifacts/openhands-cli-macos-latest/openhands release-assets/openhands-macos
|
||||
fi
|
||||
ls -la release-assets/
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: release-assets/*
|
||||
draft: true
|
||||
prerelease: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
23
.github/workflows/pypi-release.yml
vendored
23
.github/workflows/pypi-release.yml
vendored
@@ -1,14 +1,17 @@
|
||||
# Publishes the OpenHands PyPi package
|
||||
name: Publish PyPi Package
|
||||
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
reason:
|
||||
description: 'Reason for manual trigger'
|
||||
description: "What are you publishing?"
|
||||
required: true
|
||||
default: ''
|
||||
type: choice
|
||||
options:
|
||||
- app server
|
||||
- cli
|
||||
default: app server
|
||||
push:
|
||||
tags:
|
||||
- "*"
|
||||
@@ -16,8 +19,10 @@ on:
|
||||
jobs:
|
||||
release:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2204
|
||||
# Only run for tags that don't contain '-cli'
|
||||
if: startsWith(github.ref, 'refs/tags/') && !contains(github.ref, '-cli')
|
||||
# Run when manually dispatched for "app server" OR for tag pushes that don't contain '-cli'
|
||||
if: |
|
||||
(github.event_name == 'workflow_dispatch' && github.event.inputs.reason == 'app server')
|
||||
|| (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && !contains(github.ref, '-cli'))
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: useblacksmith/setup-python@v6
|
||||
@@ -38,8 +43,10 @@ jobs:
|
||||
release-cli:
|
||||
name: Publish CLI to PyPI
|
||||
runs-on: ubuntu-latest
|
||||
# Only run for tags that contain '-cli'
|
||||
if: startsWith(github.ref, 'refs/tags/') && contains(github.ref, '-cli')
|
||||
# Run when manually dispatched for "cli" OR for tag pushes that contain '-cli'
|
||||
if: |
|
||||
(github.event_name == 'workflow_dispatch' && github.event.inputs.reason == 'cli')
|
||||
|| (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && contains(github.ref, '-cli'))
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
@@ -64,4 +71,4 @@ jobs:
|
||||
- name: Publish CLI to PyPI
|
||||
working-directory: openhands-cli
|
||||
run: |
|
||||
uv publish --token ${{ secrets.PYPI_TOKEN }}
|
||||
uv publish --token ${{ secrets.PYPI_TOKEN_OPENHANDS }}
|
||||
|
||||
@@ -13,7 +13,7 @@ from integrations.solvability.models.report import SolvabilityReport
|
||||
from integrations.solvability.models.summary import SolvabilitySummary
|
||||
from integrations.utils import ENABLE_SOLVABILITY_ANALYSIS
|
||||
from pydantic import ValidationError
|
||||
from server.auth.token_manager import get_config
|
||||
from server.config import get_config
|
||||
from storage.database import session_maker
|
||||
from storage.saas_settings_store import SaasSettingsStore
|
||||
|
||||
|
||||
@@ -19,7 +19,8 @@ from integrations.utils import (
|
||||
from jinja2 import Environment
|
||||
from pydantic.dataclasses import dataclass
|
||||
from server.auth.constants import GITHUB_APP_CLIENT_ID, GITHUB_APP_PRIVATE_KEY
|
||||
from server.auth.token_manager import TokenManager, get_config
|
||||
from server.auth.token_manager import TokenManager
|
||||
from server.config import get_config
|
||||
from storage.database import session_maker
|
||||
from storage.proactive_conversation_store import ProactiveConversationStore
|
||||
from storage.saas_secrets_store import SaasSecretsStore
|
||||
|
||||
@@ -4,7 +4,8 @@ from integrations.models import Message
|
||||
from integrations.types import ResolverViewInterface, UserData
|
||||
from integrations.utils import HOST, get_oh_labels, has_exact_mention
|
||||
from jinja2 import Environment
|
||||
from server.auth.token_manager import TokenManager, get_config
|
||||
from server.auth.token_manager import TokenManager
|
||||
from server.config import get_config
|
||||
from storage.database import session_maker
|
||||
from storage.saas_secrets_store import SaasSecretsStore
|
||||
|
||||
|
||||
@@ -13,7 +13,8 @@ from server.auth.auth_error import (
|
||||
ExpiredError,
|
||||
NoCredentialsError,
|
||||
)
|
||||
from server.auth.token_manager import TokenManager, get_config
|
||||
from server.auth.token_manager import TokenManager
|
||||
from server.config import get_config
|
||||
from server.logger import logger
|
||||
from server.rate_limit import RateLimiter, create_redis_rate_limiter
|
||||
from storage.api_key_store import ApiKeyStore
|
||||
|
||||
@@ -26,6 +26,7 @@ from server.auth.constants import (
|
||||
KEYCLOAK_SERVER_URL_EXT,
|
||||
)
|
||||
from server.auth.keycloak_manager import get_keycloak_admin, get_keycloak_openid
|
||||
from server.config import get_config
|
||||
from server.logger import logger
|
||||
from sqlalchemy import String as SQLString
|
||||
from sqlalchemy import type_coerce
|
||||
@@ -35,19 +36,8 @@ from storage.github_app_installation import GithubAppInstallation
|
||||
from storage.offline_token_store import OfflineTokenStore
|
||||
from tenacity import RetryCallState, retry, retry_if_exception_type, stop_after_attempt
|
||||
|
||||
from openhands.core.config import load_openhands_config
|
||||
from openhands.integrations.service_types import ProviderType
|
||||
|
||||
# Create a function to get config to avoid circular imports
|
||||
_config = None
|
||||
|
||||
|
||||
def get_config():
|
||||
global _config
|
||||
if _config is None:
|
||||
_config = load_openhands_config()
|
||||
return _config
|
||||
|
||||
|
||||
def _before_sleep_callback(retry_state: RetryCallState) -> None:
|
||||
logger.info(f'Retry attempt {retry_state.attempt_number} for Keycloak operation')
|
||||
|
||||
@@ -19,10 +19,21 @@ from server.auth.constants import (
|
||||
GITLAB_APP_CLIENT_ID,
|
||||
)
|
||||
|
||||
from openhands.core.config.utils import load_openhands_config
|
||||
from openhands.integrations.service_types import ProviderType
|
||||
from openhands.server.config.server_config import ServerConfig
|
||||
from openhands.server.types import AppMode
|
||||
|
||||
# Create a function to get config to avoid circular imports
|
||||
_config = None
|
||||
|
||||
|
||||
def get_config():
|
||||
global _config
|
||||
if _config is None:
|
||||
_config = load_openhands_config()
|
||||
return _config
|
||||
|
||||
|
||||
def sign_token(payload: dict[str, object], jwt_secret: str, algorithm='HS256') -> str:
|
||||
"""Signs a JWT token."""
|
||||
|
||||
@@ -20,7 +20,7 @@ def token_store(session_maker, mock_config):
|
||||
|
||||
@pytest.fixture
|
||||
def token_manager():
|
||||
with patch('server.auth.token_manager.get_config') as mock_get_config:
|
||||
with patch('server.config.get_config') as mock_get_config:
|
||||
mock_config = mock_get_config.return_value
|
||||
mock_config.jwt_secret.get_secret_value.return_value = 'test_secret'
|
||||
return TokenManager(external=False)
|
||||
|
||||
@@ -8,7 +8,7 @@ from openhands.integrations.service_types import ProviderType
|
||||
|
||||
@pytest.fixture
|
||||
def token_manager():
|
||||
with patch('server.auth.token_manager.get_config') as mock_get_config:
|
||||
with patch('server.config.get_config') as mock_get_config:
|
||||
mock_config = mock_get_config.return_value
|
||||
mock_config.jwt_secret.get_secret_value.return_value = 'test_secret'
|
||||
return TokenManager(external=False)
|
||||
|
||||
@@ -20,7 +20,7 @@ export function ModalBackdrop({ children, onClose }: ModalBackdropProps) {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 flex items-center justify-center z-20">
|
||||
<div className="fixed inset-0 flex items-center justify-center z-60">
|
||||
<div
|
||||
onClick={handleClick}
|
||||
className="fixed inset-0 bg-black opacity-60"
|
||||
|
||||
@@ -72,6 +72,7 @@ def check_pyinstaller() -> bool:
|
||||
def build_executable(
|
||||
spec_file: str = 'openhands.spec',
|
||||
clean: bool = True,
|
||||
target_arch: str = None,
|
||||
) -> bool:
|
||||
"""Build the executable using PyInstaller."""
|
||||
if clean:
|
||||
@@ -83,8 +84,27 @@ def build_executable(
|
||||
|
||||
print(f'🔨 Building executable using {spec_file}...')
|
||||
|
||||
# Handle target architecture for macOS by modifying the spec file
|
||||
original_spec_content = None
|
||||
if target_arch and sys.platform == 'darwin':
|
||||
print(f'🎯 Building for macOS target architecture: {target_arch}')
|
||||
|
||||
# Read the original spec file
|
||||
with open(spec_file, 'r') as f:
|
||||
original_spec_content = f.read()
|
||||
|
||||
# Replace target_arch=None with target_arch='<arch>'
|
||||
modified_spec_content = original_spec_content.replace(
|
||||
'target_arch=None',
|
||||
f"target_arch='{target_arch}'"
|
||||
)
|
||||
|
||||
# Write the modified spec file
|
||||
with open(spec_file, 'w') as f:
|
||||
f.write(modified_spec_content)
|
||||
|
||||
try:
|
||||
# Run PyInstaller with uv
|
||||
# Run PyInstaller with uv (no --target-arch flag needed with spec file)
|
||||
cmd = ['uv', 'run', 'pyinstaller', spec_file, '--clean']
|
||||
|
||||
print(f'Running: {" ".join(cmd)}')
|
||||
@@ -113,6 +133,12 @@ def build_executable(
|
||||
if e.stderr:
|
||||
print('STDERR:', e.stderr)
|
||||
return False
|
||||
finally:
|
||||
# Restore the original spec file if we modified it
|
||||
if original_spec_content is not None:
|
||||
with open(spec_file, 'w') as f:
|
||||
f.write(original_spec_content)
|
||||
print(f'🔄 Restored original {spec_file}')
|
||||
|
||||
|
||||
# =================================================
|
||||
@@ -254,6 +280,10 @@ def main() -> int:
|
||||
action='store_true',
|
||||
help='Install PyInstaller using uv before building',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--target-arch',
|
||||
help='Target architecture for macOS builds (x86_64, arm64, universal2)',
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--no-build', action='store_true', help='Skip testing the built executable'
|
||||
@@ -270,7 +300,9 @@ def main() -> int:
|
||||
return 1
|
||||
|
||||
# Build the executable
|
||||
if not args.no_build and not build_executable(args.spec, clean=not args.no_clean):
|
||||
if not args.no_build and not build_executable(
|
||||
args.spec, clean=not args.no_clean, target_arch=args.target_arch
|
||||
):
|
||||
return 1
|
||||
|
||||
# Test the executable
|
||||
|
||||
@@ -4,7 +4,7 @@ requires = [ "hatchling>=1.25" ]
|
||||
|
||||
[project]
|
||||
name = "openhands"
|
||||
version = "0.1.0"
|
||||
version = "1.0.0"
|
||||
description = "OpenHands CLI - Terminal User Interface for OpenHands AI Agent"
|
||||
readme = "README.md"
|
||||
license = { text = "MIT" }
|
||||
|
||||
Reference in New Issue
Block a user