mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
fix(CLI): alias persistence issue (#9828)
Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
parent
6d7ab8a022
commit
a7245f2de2
@ -17,7 +17,9 @@ from openhands.cli.settings import modify_llm_settings_basic
|
||||
from openhands.cli.shell_config import (
|
||||
ShellConfigManager,
|
||||
add_aliases_to_shell_config,
|
||||
alias_setup_declined,
|
||||
aliases_exist_in_shell_config,
|
||||
mark_alias_setup_declined,
|
||||
)
|
||||
from openhands.cli.tui import (
|
||||
UsageMetrics,
|
||||
@ -387,106 +389,86 @@ def run_alias_setup_flow(config: OpenHandsConfig) -> None:
|
||||
|
||||
Prompts the user to set up aliases for 'openhands' and 'oh' commands.
|
||||
Handles existing aliases by offering to keep or remove them.
|
||||
|
||||
Args:
|
||||
config: OpenHands configuration
|
||||
"""
|
||||
print_formatted_text('')
|
||||
print_formatted_text(HTML('<gold>🚀 Welcome to OpenHands CLI!</gold>'))
|
||||
print_formatted_text('')
|
||||
|
||||
# Check if aliases already exist
|
||||
if aliases_exist_in_shell_config():
|
||||
print_formatted_text(
|
||||
HTML(
|
||||
'<grey>We detected existing OpenHands aliases in your shell configuration.</grey>'
|
||||
)
|
||||
# Show the normal setup flow
|
||||
print_formatted_text(
|
||||
HTML('<grey>Would you like to set up convenient shell aliases?</grey>')
|
||||
)
|
||||
print_formatted_text('')
|
||||
print_formatted_text(
|
||||
HTML('<grey>This will add the following aliases to your shell profile:</grey>')
|
||||
)
|
||||
print_formatted_text(
|
||||
HTML(
|
||||
'<grey> • <b>openhands</b> → uvx --python 3.12 --from openhands-ai openhands</grey>'
|
||||
)
|
||||
print_formatted_text('')
|
||||
print_formatted_text(
|
||||
HTML(
|
||||
'<grey> • <b>openhands</b> → uvx --python 3.12 --from openhands-ai openhands</grey>'
|
||||
)
|
||||
)
|
||||
print_formatted_text(
|
||||
HTML(
|
||||
'<grey> • <b>oh</b> → uvx --python 3.12 --from openhands-ai openhands</grey>'
|
||||
)
|
||||
print_formatted_text(
|
||||
HTML(
|
||||
'<grey> • <b>oh</b> → uvx --python 3.12 --from openhands-ai openhands</grey>'
|
||||
)
|
||||
)
|
||||
print_formatted_text('')
|
||||
print_formatted_text(
|
||||
HTML(
|
||||
'<ansiyellow>⚠️ Note: This requires uv to be installed first.</ansiyellow>'
|
||||
)
|
||||
print_formatted_text('')
|
||||
print_formatted_text(
|
||||
HTML('<ansigreen>✅ Aliases are already configured.</ansigreen>')
|
||||
)
|
||||
print_formatted_text(
|
||||
HTML(
|
||||
'<ansiyellow> Installation guide: https://docs.astral.sh/uv/getting-started/installation</ansiyellow>'
|
||||
)
|
||||
return # Exit early since aliases already exist
|
||||
else:
|
||||
# No existing aliases, show the normal setup flow
|
||||
print_formatted_text(
|
||||
HTML('<grey>Would you like to set up convenient shell aliases?</grey>')
|
||||
)
|
||||
print_formatted_text('')
|
||||
print_formatted_text(
|
||||
HTML(
|
||||
'<grey>This will add the following aliases to your shell profile:</grey>'
|
||||
)
|
||||
)
|
||||
print_formatted_text(
|
||||
HTML(
|
||||
'<grey> • <b>openhands</b> → uvx --python 3.12 --from openhands-ai openhands</grey>'
|
||||
)
|
||||
)
|
||||
print_formatted_text(
|
||||
HTML(
|
||||
'<grey> • <b>oh</b> → uvx --python 3.12 --from openhands-ai openhands</grey>'
|
||||
)
|
||||
)
|
||||
print_formatted_text('')
|
||||
print_formatted_text(
|
||||
HTML(
|
||||
'<ansiyellow>⚠️ Note: This requires uv to be installed first.</ansiyellow>'
|
||||
)
|
||||
)
|
||||
print_formatted_text(
|
||||
HTML(
|
||||
'<ansiyellow> Installation guide: https://docs.astral.sh/uv/getting-started/installation</ansiyellow>'
|
||||
)
|
||||
)
|
||||
print_formatted_text('')
|
||||
)
|
||||
print_formatted_text('')
|
||||
|
||||
# Use cli_confirm to get user choice
|
||||
choice = cli_confirm(
|
||||
config,
|
||||
'Set up shell aliases?',
|
||||
['Yes, set up aliases', 'No, skip this step'],
|
||||
)
|
||||
# Use cli_confirm to get user choice
|
||||
choice = cli_confirm(
|
||||
config,
|
||||
'Set up shell aliases?',
|
||||
['Yes, set up aliases', 'No, skip this step'],
|
||||
)
|
||||
|
||||
if choice == 0: # User chose "Yes"
|
||||
success = add_aliases_to_shell_config()
|
||||
if success:
|
||||
print_formatted_text('')
|
||||
print_formatted_text(
|
||||
HTML('<ansigreen>✅ Aliases added successfully!</ansigreen>')
|
||||
if choice == 0: # User chose "Yes"
|
||||
success = add_aliases_to_shell_config()
|
||||
if success:
|
||||
print_formatted_text('')
|
||||
print_formatted_text(
|
||||
HTML('<ansigreen>✅ Aliases added successfully!</ansigreen>')
|
||||
)
|
||||
|
||||
# Get the appropriate reload command using the shell config manager
|
||||
shell_manager = ShellConfigManager()
|
||||
reload_cmd = shell_manager.get_reload_command()
|
||||
|
||||
print_formatted_text(
|
||||
HTML(
|
||||
f'<grey>Run <b>{reload_cmd}</b> (or restart your terminal) to use the new aliases.</grey>'
|
||||
)
|
||||
|
||||
# Get the appropriate reload command using the shell config manager
|
||||
shell_manager = ShellConfigManager()
|
||||
reload_cmd = shell_manager.get_reload_command()
|
||||
|
||||
print_formatted_text(
|
||||
HTML(
|
||||
f'<grey>Run <b>{reload_cmd}</b> (or restart your terminal) to use the new aliases.</grey>'
|
||||
)
|
||||
)
|
||||
else:
|
||||
print_formatted_text('')
|
||||
print_formatted_text(
|
||||
HTML(
|
||||
'<ansired>❌ Failed to add aliases. You can set them up manually later.</ansired>'
|
||||
)
|
||||
)
|
||||
else: # User chose "No"
|
||||
)
|
||||
else:
|
||||
print_formatted_text('')
|
||||
print_formatted_text(
|
||||
HTML(
|
||||
'<grey>Skipped alias setup. You can run this setup again anytime.</grey>'
|
||||
'<ansired>❌ Failed to add aliases. You can set them up manually later.</ansired>'
|
||||
)
|
||||
)
|
||||
else: # User chose "No"
|
||||
# Mark that the user has declined alias setup
|
||||
mark_alias_setup_declined()
|
||||
|
||||
print_formatted_text('')
|
||||
print_formatted_text(
|
||||
HTML(
|
||||
'<grey>Skipped alias setup. You can run this setup again anytime.</grey>'
|
||||
)
|
||||
)
|
||||
|
||||
print_formatted_text('')
|
||||
|
||||
@ -583,15 +565,23 @@ async def main_with_loop(loop: asyncio.AbstractEventLoop) -> None:
|
||||
finalize_config(config)
|
||||
|
||||
# Check if we should show the alias setup flow
|
||||
# Only show it if aliases don't exist in the shell configuration
|
||||
# and we're in an interactive environment (not during tests or CI)
|
||||
if not aliases_exist_in_shell_config() and sys.stdin.isatty():
|
||||
# Clear the terminal if we haven't shown a banner yet
|
||||
# Only show it if:
|
||||
# 1. Aliases don't exist in the shell configuration
|
||||
# 2. User hasn't previously declined alias setup
|
||||
# 3. We're in an interactive environment (not during tests or CI)
|
||||
should_show_alias_setup = (
|
||||
not aliases_exist_in_shell_config()
|
||||
and not alias_setup_declined()
|
||||
and sys.stdin.isatty()
|
||||
)
|
||||
|
||||
if should_show_alias_setup:
|
||||
# Clear the terminal if we haven't shown a banner yet (i.e., setup flow didn't run)
|
||||
if not banner_shown:
|
||||
clear()
|
||||
|
||||
run_alias_setup_flow(config)
|
||||
banner_shown = True
|
||||
# Don't set banner_shown = True here, so the ASCII art banner will still be shown
|
||||
|
||||
# TODO: Set working directory from config or use current working directory?
|
||||
current_dir = config.workspace_base
|
||||
|
||||
@ -277,3 +277,21 @@ def get_shell_config_path() -> Path:
|
||||
"""Get the path to the shell configuration file."""
|
||||
manager = ShellConfigManager()
|
||||
return manager.get_shell_config_path()
|
||||
|
||||
|
||||
def alias_setup_declined() -> bool:
|
||||
"""Check if the user has previously declined alias setup.
|
||||
|
||||
Returns:
|
||||
True if user has declined alias setup, False otherwise.
|
||||
"""
|
||||
marker_file = Path.home() / '.openhands' / '.cli_alias_setup_declined'
|
||||
return marker_file.exists()
|
||||
|
||||
|
||||
def mark_alias_setup_declined() -> None:
|
||||
"""Mark that the user has declined alias setup."""
|
||||
openhands_dir = Path.home() / '.openhands'
|
||||
openhands_dir.mkdir(exist_ok=True)
|
||||
marker_file = openhands_dir / '.cli_alias_setup_declined'
|
||||
marker_file.touch()
|
||||
|
||||
@ -4,12 +4,16 @@ import tempfile
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from openhands.cli.main import alias_setup_declined as main_alias_setup_declined
|
||||
from openhands.cli.main import aliases_exist_in_shell_config, run_alias_setup_flow
|
||||
from openhands.cli.shell_config import (
|
||||
ShellConfigManager,
|
||||
add_aliases_to_shell_config,
|
||||
aliases_exist_in_shell_config,
|
||||
alias_setup_declined,
|
||||
get_shell_config_path,
|
||||
mark_alias_setup_declined,
|
||||
)
|
||||
from openhands.core.config import OpenHandsConfig
|
||||
|
||||
|
||||
def test_get_shell_config_path_no_files_fallback():
|
||||
@ -244,3 +248,121 @@ def test_shell_config_manager_template_rendering():
|
||||
assert 'test-command' in content
|
||||
assert 'alias openhands="test-command"' in content
|
||||
assert 'alias oh="test-command"' in content
|
||||
|
||||
|
||||
def test_alias_setup_declined_false():
|
||||
"""Test alias setup declined check when marker file doesn't exist."""
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
with patch('openhands.cli.shell_config.Path.home', return_value=Path(temp_dir)):
|
||||
assert alias_setup_declined() is False
|
||||
|
||||
|
||||
def test_alias_setup_declined_true():
|
||||
"""Test alias setup declined check when marker file exists."""
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
with patch('openhands.cli.shell_config.Path.home', return_value=Path(temp_dir)):
|
||||
# Create the marker file
|
||||
mark_alias_setup_declined()
|
||||
assert alias_setup_declined() is True
|
||||
|
||||
|
||||
def test_mark_alias_setup_declined():
|
||||
"""Test marking alias setup as declined creates the marker file."""
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
with patch('openhands.cli.shell_config.Path.home', return_value=Path(temp_dir)):
|
||||
# Initially should be False
|
||||
assert alias_setup_declined() is False
|
||||
|
||||
# Mark as declined
|
||||
mark_alias_setup_declined()
|
||||
|
||||
# Should now be True
|
||||
assert alias_setup_declined() is True
|
||||
|
||||
# Verify the file exists
|
||||
marker_file = Path(temp_dir) / '.openhands' / '.cli_alias_setup_declined'
|
||||
assert marker_file.exists()
|
||||
|
||||
|
||||
def test_alias_setup_declined_persisted():
|
||||
"""Test that when user declines alias setup, their choice is persisted."""
|
||||
config = OpenHandsConfig()
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
with patch('openhands.cli.shell_config.Path.home', return_value=Path(temp_dir)):
|
||||
with patch('shellingham.detect_shell', return_value=('bash', 'bash')):
|
||||
with patch(
|
||||
'openhands.cli.shell_config.aliases_exist_in_shell_config',
|
||||
return_value=False,
|
||||
):
|
||||
with patch(
|
||||
'openhands.cli.main.cli_confirm', return_value=1
|
||||
): # User chooses "No"
|
||||
with patch('prompt_toolkit.print_formatted_text'):
|
||||
# Initially, user hasn't declined
|
||||
assert not alias_setup_declined()
|
||||
|
||||
# Run the alias setup flow
|
||||
run_alias_setup_flow(config)
|
||||
|
||||
# After declining, the marker should be set
|
||||
assert alias_setup_declined()
|
||||
|
||||
|
||||
def test_alias_setup_skipped_when_previously_declined():
|
||||
"""Test that alias setup is skipped when user has previously declined."""
|
||||
OpenHandsConfig()
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
with patch('openhands.cli.shell_config.Path.home', return_value=Path(temp_dir)):
|
||||
# Mark that user has previously declined
|
||||
mark_alias_setup_declined()
|
||||
assert alias_setup_declined()
|
||||
|
||||
with patch('shellingham.detect_shell', return_value=('bash', 'bash')):
|
||||
with patch(
|
||||
'openhands.cli.shell_config.aliases_exist_in_shell_config',
|
||||
return_value=False,
|
||||
):
|
||||
with patch('openhands.cli.main.cli_confirm'):
|
||||
with patch('prompt_toolkit.print_formatted_text'):
|
||||
# This should not show the setup flow since user previously declined
|
||||
# We test this by checking the main logic conditions
|
||||
|
||||
should_show = (
|
||||
not aliases_exist_in_shell_config()
|
||||
and not main_alias_setup_declined()
|
||||
)
|
||||
|
||||
assert not should_show, (
|
||||
'Alias setup should be skipped when user previously declined'
|
||||
)
|
||||
|
||||
|
||||
def test_alias_setup_accepted_does_not_set_declined_flag():
|
||||
"""Test that when user accepts alias setup, no declined marker is created."""
|
||||
config = OpenHandsConfig()
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
with patch('openhands.cli.shell_config.Path.home', return_value=Path(temp_dir)):
|
||||
with patch('shellingham.detect_shell', return_value=('bash', 'bash')):
|
||||
with patch(
|
||||
'openhands.cli.shell_config.aliases_exist_in_shell_config',
|
||||
return_value=False,
|
||||
):
|
||||
with patch(
|
||||
'openhands.cli.main.cli_confirm', return_value=0
|
||||
): # User chooses "Yes"
|
||||
with patch(
|
||||
'openhands.cli.shell_config.add_aliases_to_shell_config',
|
||||
return_value=True,
|
||||
):
|
||||
with patch('prompt_toolkit.print_formatted_text'):
|
||||
# Initially, user hasn't declined
|
||||
assert not alias_setup_declined()
|
||||
|
||||
# Run the alias setup flow
|
||||
run_alias_setup_flow(config)
|
||||
|
||||
# After accepting, the declined marker should still be False
|
||||
assert not alias_setup_declined()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user