openhands 0.0.0__py3-none-any.whl → 1.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of openhands might be problematic. Click here for more details.

Files changed (124) hide show
  1. openhands-1.0.1.dist-info/METADATA +52 -0
  2. openhands-1.0.1.dist-info/RECORD +31 -0
  3. {openhands-0.0.0.dist-info → openhands-1.0.1.dist-info}/WHEEL +1 -2
  4. openhands-1.0.1.dist-info/entry_points.txt +2 -0
  5. openhands_cli/__init__.py +8 -0
  6. openhands_cli/agent_chat.py +186 -0
  7. openhands_cli/argparsers/main_parser.py +56 -0
  8. openhands_cli/argparsers/serve_parser.py +31 -0
  9. openhands_cli/gui_launcher.py +220 -0
  10. openhands_cli/listeners/__init__.py +4 -0
  11. openhands_cli/listeners/loading_listener.py +63 -0
  12. openhands_cli/listeners/pause_listener.py +83 -0
  13. openhands_cli/llm_utils.py +57 -0
  14. openhands_cli/locations.py +13 -0
  15. openhands_cli/pt_style.py +30 -0
  16. openhands_cli/runner.py +178 -0
  17. openhands_cli/setup.py +116 -0
  18. openhands_cli/simple_main.py +59 -0
  19. openhands_cli/tui/__init__.py +5 -0
  20. openhands_cli/tui/settings/mcp_screen.py +217 -0
  21. openhands_cli/tui/settings/settings_screen.py +202 -0
  22. openhands_cli/tui/settings/store.py +93 -0
  23. openhands_cli/tui/status.py +109 -0
  24. openhands_cli/tui/tui.py +100 -0
  25. openhands_cli/tui/utils.py +14 -0
  26. openhands_cli/user_actions/__init__.py +17 -0
  27. openhands_cli/user_actions/agent_action.py +95 -0
  28. openhands_cli/user_actions/exit_session.py +18 -0
  29. openhands_cli/user_actions/settings_action.py +171 -0
  30. openhands_cli/user_actions/types.py +18 -0
  31. openhands_cli/user_actions/utils.py +199 -0
  32. openhands/__init__.py +0 -1
  33. openhands/sdk/__init__.py +0 -45
  34. openhands/sdk/agent/__init__.py +0 -8
  35. openhands/sdk/agent/agent/__init__.py +0 -6
  36. openhands/sdk/agent/agent/agent.py +0 -349
  37. openhands/sdk/agent/base.py +0 -103
  38. openhands/sdk/context/__init__.py +0 -28
  39. openhands/sdk/context/agent_context.py +0 -153
  40. openhands/sdk/context/condenser/__init__.py +0 -5
  41. openhands/sdk/context/condenser/condenser.py +0 -73
  42. openhands/sdk/context/condenser/no_op_condenser.py +0 -13
  43. openhands/sdk/context/manager.py +0 -5
  44. openhands/sdk/context/microagents/__init__.py +0 -26
  45. openhands/sdk/context/microagents/exceptions.py +0 -11
  46. openhands/sdk/context/microagents/microagent.py +0 -345
  47. openhands/sdk/context/microagents/types.py +0 -70
  48. openhands/sdk/context/utils/__init__.py +0 -8
  49. openhands/sdk/context/utils/prompt.py +0 -52
  50. openhands/sdk/context/view.py +0 -116
  51. openhands/sdk/conversation/__init__.py +0 -12
  52. openhands/sdk/conversation/conversation.py +0 -207
  53. openhands/sdk/conversation/state.py +0 -50
  54. openhands/sdk/conversation/types.py +0 -6
  55. openhands/sdk/conversation/visualizer.py +0 -300
  56. openhands/sdk/event/__init__.py +0 -27
  57. openhands/sdk/event/base.py +0 -148
  58. openhands/sdk/event/condenser.py +0 -49
  59. openhands/sdk/event/llm_convertible.py +0 -265
  60. openhands/sdk/event/types.py +0 -5
  61. openhands/sdk/event/user_action.py +0 -12
  62. openhands/sdk/event/utils.py +0 -30
  63. openhands/sdk/llm/__init__.py +0 -19
  64. openhands/sdk/llm/exceptions.py +0 -108
  65. openhands/sdk/llm/llm.py +0 -867
  66. openhands/sdk/llm/llm_registry.py +0 -116
  67. openhands/sdk/llm/message.py +0 -216
  68. openhands/sdk/llm/metadata.py +0 -34
  69. openhands/sdk/llm/utils/fn_call_converter.py +0 -1049
  70. openhands/sdk/llm/utils/metrics.py +0 -311
  71. openhands/sdk/llm/utils/model_features.py +0 -153
  72. openhands/sdk/llm/utils/retry_mixin.py +0 -122
  73. openhands/sdk/llm/utils/telemetry.py +0 -252
  74. openhands/sdk/logger.py +0 -167
  75. openhands/sdk/mcp/__init__.py +0 -20
  76. openhands/sdk/mcp/client.py +0 -113
  77. openhands/sdk/mcp/definition.py +0 -69
  78. openhands/sdk/mcp/tool.py +0 -104
  79. openhands/sdk/mcp/utils.py +0 -59
  80. openhands/sdk/tests/llm/test_llm.py +0 -447
  81. openhands/sdk/tests/llm/test_llm_fncall_converter.py +0 -691
  82. openhands/sdk/tests/llm/test_model_features.py +0 -221
  83. openhands/sdk/tool/__init__.py +0 -30
  84. openhands/sdk/tool/builtins/__init__.py +0 -34
  85. openhands/sdk/tool/builtins/finish.py +0 -57
  86. openhands/sdk/tool/builtins/think.py +0 -60
  87. openhands/sdk/tool/schema.py +0 -236
  88. openhands/sdk/tool/security_prompt.py +0 -5
  89. openhands/sdk/tool/tool.py +0 -142
  90. openhands/sdk/utils/__init__.py +0 -14
  91. openhands/sdk/utils/discriminated_union.py +0 -210
  92. openhands/sdk/utils/json.py +0 -48
  93. openhands/sdk/utils/truncate.py +0 -44
  94. openhands/tools/__init__.py +0 -44
  95. openhands/tools/execute_bash/__init__.py +0 -30
  96. openhands/tools/execute_bash/constants.py +0 -31
  97. openhands/tools/execute_bash/definition.py +0 -166
  98. openhands/tools/execute_bash/impl.py +0 -38
  99. openhands/tools/execute_bash/metadata.py +0 -101
  100. openhands/tools/execute_bash/terminal/__init__.py +0 -22
  101. openhands/tools/execute_bash/terminal/factory.py +0 -113
  102. openhands/tools/execute_bash/terminal/interface.py +0 -189
  103. openhands/tools/execute_bash/terminal/subprocess_terminal.py +0 -412
  104. openhands/tools/execute_bash/terminal/terminal_session.py +0 -492
  105. openhands/tools/execute_bash/terminal/tmux_terminal.py +0 -160
  106. openhands/tools/execute_bash/utils/command.py +0 -150
  107. openhands/tools/str_replace_editor/__init__.py +0 -17
  108. openhands/tools/str_replace_editor/definition.py +0 -158
  109. openhands/tools/str_replace_editor/editor.py +0 -683
  110. openhands/tools/str_replace_editor/exceptions.py +0 -41
  111. openhands/tools/str_replace_editor/impl.py +0 -66
  112. openhands/tools/str_replace_editor/utils/__init__.py +0 -0
  113. openhands/tools/str_replace_editor/utils/config.py +0 -2
  114. openhands/tools/str_replace_editor/utils/constants.py +0 -9
  115. openhands/tools/str_replace_editor/utils/encoding.py +0 -135
  116. openhands/tools/str_replace_editor/utils/file_cache.py +0 -154
  117. openhands/tools/str_replace_editor/utils/history.py +0 -122
  118. openhands/tools/str_replace_editor/utils/shell.py +0 -72
  119. openhands/tools/task_tracker/__init__.py +0 -16
  120. openhands/tools/task_tracker/definition.py +0 -336
  121. openhands/tools/utils/__init__.py +0 -1
  122. openhands-0.0.0.dist-info/METADATA +0 -3
  123. openhands-0.0.0.dist-info/RECORD +0 -94
  124. openhands-0.0.0.dist-info/top_level.txt +0 -1
@@ -0,0 +1,83 @@
1
+ import threading
2
+ from collections.abc import Callable, Iterator
3
+ from contextlib import contextmanager
4
+
5
+ from prompt_toolkit import HTML, print_formatted_text
6
+ from prompt_toolkit.input import Input, create_input
7
+ from prompt_toolkit.keys import Keys
8
+
9
+ from openhands.sdk import BaseConversation
10
+
11
+
12
+ class PauseListener(threading.Thread):
13
+ """Background key listener that triggers pause on Ctrl-P.
14
+
15
+ Starts and stops around agent run() loops to avoid interfering with user prompts.
16
+ """
17
+
18
+ def __init__(
19
+ self,
20
+ on_pause: Callable,
21
+ input_source: Input | None = None, # used to pipe inputs for unit tests
22
+ ):
23
+ super().__init__(daemon=True)
24
+ self.on_pause = on_pause
25
+ self._stop_event = threading.Event()
26
+ self._pause_event = threading.Event()
27
+ self._input = input_source or create_input()
28
+
29
+ def _detect_pause_key_presses(self) -> bool:
30
+ pause_detected = False
31
+
32
+ for key_press in self._input.read_keys():
33
+ pause_detected = pause_detected or key_press.key == Keys.ControlP
34
+ pause_detected = pause_detected or key_press.key == Keys.ControlC
35
+ pause_detected = pause_detected or key_press.key == Keys.ControlD
36
+
37
+ return pause_detected
38
+
39
+ def _execute_pause(self) -> None:
40
+ self._pause_event.set() # Mark pause event occurred
41
+ print_formatted_text(HTML(''))
42
+ print_formatted_text(
43
+ HTML('<gold>Pausing agent once step is completed...</gold>')
44
+ )
45
+ try:
46
+ self.on_pause()
47
+ except Exception:
48
+ pass
49
+
50
+ def run(self) -> None:
51
+ try:
52
+ with self._input.raw_mode():
53
+ # User hasn't paused and pause listener hasn't been shut down
54
+ while not (self.is_paused() or self.is_stopped()):
55
+ if self._detect_pause_key_presses():
56
+ self._execute_pause()
57
+ finally:
58
+ try:
59
+ self._input.close()
60
+ except Exception:
61
+ pass
62
+
63
+ def stop(self) -> None:
64
+ self._stop_event.set()
65
+
66
+ def is_stopped(self) -> bool:
67
+ return self._stop_event.is_set()
68
+
69
+ def is_paused(self) -> bool:
70
+ return self._pause_event.is_set()
71
+
72
+
73
+ @contextmanager
74
+ def pause_listener(
75
+ conversation: BaseConversation, input_source: Input | None = None
76
+ ) -> Iterator[PauseListener]:
77
+ """Ensure PauseListener always starts/stops cleanly."""
78
+ listener = PauseListener(on_pause=conversation.pause, input_source=input_source)
79
+ listener.start()
80
+ try:
81
+ yield listener
82
+ finally:
83
+ listener.stop()
@@ -0,0 +1,57 @@
1
+ """Utility functions for LLM configuration in OpenHands CLI."""
2
+
3
+ import os
4
+ from typing import Any
5
+
6
+
7
+ def get_llm_metadata(
8
+ model_name: str,
9
+ llm_type: str,
10
+ session_id: str | None = None,
11
+ user_id: str | None = None,
12
+ ) -> dict[str, Any]:
13
+ """
14
+ Generate LLM metadata for OpenHands CLI.
15
+
16
+ Args:
17
+ model_name: Name of the LLM model
18
+ agent_name: Name of the agent (defaults to "openhands")
19
+ session_id: Optional session identifier
20
+ user_id: Optional user identifier
21
+
22
+ Returns:
23
+ Dictionary containing metadata for LLM initialization
24
+ """
25
+ # Import here to avoid circular imports
26
+ openhands_sdk_version: str = 'n/a'
27
+ try:
28
+ import openhands.sdk
29
+
30
+ openhands_sdk_version = openhands.sdk.__version__
31
+ except (ModuleNotFoundError, AttributeError):
32
+ pass
33
+
34
+ openhands_tools_version: str = 'n/a'
35
+ try:
36
+ import openhands.tools
37
+
38
+ openhands_tools_version = openhands.tools.__version__
39
+ except (ModuleNotFoundError, AttributeError):
40
+ pass
41
+
42
+ metadata = {
43
+ 'trace_version': openhands_sdk_version,
44
+ 'tags': [
45
+ 'app:openhands',
46
+ f'model:{model_name}',
47
+ f'type:{llm_type}',
48
+ f'web_host:{os.environ.get("WEB_HOST", "unspecified")}',
49
+ f'openhands_sdk_version:{openhands_sdk_version}',
50
+ f'openhands_tools_version:{openhands_tools_version}',
51
+ ],
52
+ }
53
+ if session_id is not None:
54
+ metadata['session_id'] = session_id
55
+ if user_id is not None:
56
+ metadata['trace_user_id'] = user_id
57
+ return metadata
@@ -0,0 +1,13 @@
1
+ import os
2
+
3
+ # Configuration directory for storing agent settings and CLI configuration
4
+ PERSISTENCE_DIR = os.path.expanduser('~/.openhands')
5
+ CONVERSATIONS_DIR = os.path.join(PERSISTENCE_DIR, 'conversations')
6
+
7
+ # Working directory for agent operations (current directory where CLI is run)
8
+ WORK_DIR = os.getcwd()
9
+
10
+ AGENT_SETTINGS_PATH = 'agent_settings.json'
11
+
12
+ # MCP configuration file (relative to PERSISTENCE_DIR)
13
+ MCP_CONFIG_FILE = 'mcp.json'
@@ -0,0 +1,30 @@
1
+ from prompt_toolkit.styles import Style, merge_styles
2
+ from prompt_toolkit.styles.base import BaseStyle
3
+ from prompt_toolkit.styles.defaults import default_ui_style
4
+
5
+ # Centralized helper for CLI styles so we can safely merge our custom colors
6
+ # with prompt_toolkit's default UI style. This preserves completion menu and
7
+ # fuzzy-match visibility across different terminal themes (e.g., Ubuntu).
8
+
9
+ COLOR_GOLD = '#FFD700'
10
+ COLOR_GREY = '#808080'
11
+ COLOR_AGENT_BLUE = '#4682B4' # Steel blue - readable on light/dark backgrounds
12
+
13
+
14
+ def get_cli_style() -> BaseStyle:
15
+ base = default_ui_style()
16
+ custom = Style.from_dict(
17
+ {
18
+ 'gold': COLOR_GOLD,
19
+ 'grey': COLOR_GREY,
20
+ 'prompt': f'{COLOR_GOLD} bold',
21
+ # Ensure good contrast for fuzzy matches on the selected completion row
22
+ # across terminals/themes (e.g., Ubuntu GNOME, Alacritty, Kitty).
23
+ # See https://github.com/All-Hands-AI/OpenHands/issues/10330
24
+ 'completion-menu.completion.current fuzzymatch.outside': 'fg:#ffffff bg:#888888',
25
+ 'selected': COLOR_GOLD,
26
+ 'risk-high': '#FF0000 bold', # Red bold for HIGH risk
27
+ 'placeholder': '#888888 italic',
28
+ }
29
+ )
30
+ return merge_styles([base, custom])
@@ -0,0 +1,178 @@
1
+ from prompt_toolkit import HTML, print_formatted_text
2
+
3
+ from openhands.sdk import BaseConversation, Message
4
+ from openhands.sdk.conversation.state import AgentExecutionStatus, ConversationState
5
+ from openhands.sdk.security.confirmation_policy import (
6
+ AlwaysConfirm,
7
+ ConfirmationPolicyBase,
8
+ ConfirmRisky,
9
+ NeverConfirm,
10
+ )
11
+ from openhands_cli.listeners.pause_listener import PauseListener, pause_listener
12
+ from openhands_cli.user_actions import ask_user_confirmation
13
+ from openhands_cli.user_actions.types import UserConfirmation
14
+ from openhands_cli.setup import setup_conversation
15
+
16
+
17
+ class ConversationRunner:
18
+ """Handles the conversation state machine logic cleanly."""
19
+
20
+ def __init__(self, conversation: BaseConversation):
21
+ self.conversation = conversation
22
+
23
+ @property
24
+ def is_confirmation_mode_active(self):
25
+ return self.conversation.is_confirmation_mode_active
26
+
27
+ def toggle_confirmation_mode(self):
28
+ new_confirmation_mode_state = not self.is_confirmation_mode_active
29
+
30
+ self.conversation = setup_conversation(
31
+ self.conversation.id,
32
+ include_security_analyzer=new_confirmation_mode_state
33
+ )
34
+
35
+ if new_confirmation_mode_state:
36
+ # Enable confirmation mode: set AlwaysConfirm policy
37
+ self.set_confirmation_policy(AlwaysConfirm())
38
+ else:
39
+ # Disable confirmation mode: set NeverConfirm policy and remove security analyzer
40
+ self.set_confirmation_policy(NeverConfirm())
41
+
42
+ def set_confirmation_policy(
43
+ self, confirmation_policy: ConfirmationPolicyBase
44
+ ) -> None:
45
+ self.conversation.set_confirmation_policy(confirmation_policy)
46
+
47
+
48
+ def _start_listener(self) -> None:
49
+ self.listener = PauseListener(on_pause=self.conversation.pause)
50
+ self.listener.start()
51
+
52
+ def _print_run_status(self) -> None:
53
+ print_formatted_text('')
54
+ if self.conversation.state.agent_status == AgentExecutionStatus.PAUSED:
55
+ print_formatted_text(
56
+ HTML(
57
+ '<yellow>Resuming paused conversation...</yellow><grey> (Press Ctrl-P to pause)</grey>'
58
+ )
59
+ )
60
+
61
+ else:
62
+ print_formatted_text(
63
+ HTML(
64
+ '<yellow>Agent running...</yellow><grey> (Press Ctrl-P to pause)</grey>'
65
+ )
66
+ )
67
+ print_formatted_text('')
68
+
69
+ def process_message(self, message: Message | None) -> None:
70
+ """Process a user message through the conversation.
71
+
72
+ Args:
73
+ message: The user message to process
74
+ """
75
+
76
+ self._print_run_status()
77
+
78
+ # Send message to conversation
79
+ if message:
80
+ self.conversation.send_message(message)
81
+
82
+ if self.is_confirmation_mode_active:
83
+ self._run_with_confirmation()
84
+ else:
85
+ self._run_without_confirmation()
86
+
87
+ def _run_without_confirmation(self) -> None:
88
+ with pause_listener(self.conversation):
89
+ self.conversation.run()
90
+
91
+ def _run_with_confirmation(self) -> None:
92
+ # If agent was paused, resume with confirmation request
93
+ if (
94
+ self.conversation.state.agent_status
95
+ == AgentExecutionStatus.WAITING_FOR_CONFIRMATION
96
+ ):
97
+ user_confirmation = self._handle_confirmation_request()
98
+ if user_confirmation == UserConfirmation.DEFER:
99
+ return
100
+
101
+ while True:
102
+ with pause_listener(self.conversation) as listener:
103
+ self.conversation.run()
104
+
105
+ if listener.is_paused():
106
+ break
107
+
108
+ # In confirmation mode, agent either finishes or waits for user confirmation
109
+ if self.conversation.state.agent_status == AgentExecutionStatus.FINISHED:
110
+ break
111
+
112
+ elif (
113
+ self.conversation.state.agent_status
114
+ == AgentExecutionStatus.WAITING_FOR_CONFIRMATION
115
+ ):
116
+ user_confirmation = self._handle_confirmation_request()
117
+ if user_confirmation == UserConfirmation.DEFER:
118
+ return
119
+
120
+ else:
121
+ raise Exception('Infinite loop')
122
+
123
+ def _handle_confirmation_request(self) -> UserConfirmation:
124
+ """Handle confirmation request from user.
125
+
126
+ Returns:
127
+ UserConfirmation indicating the user's choice
128
+ """
129
+
130
+ pending_actions = ConversationState.get_unmatched_actions(
131
+ self.conversation.state.events
132
+ )
133
+ if not pending_actions:
134
+ return UserConfirmation.ACCEPT
135
+
136
+ result = ask_user_confirmation(
137
+ pending_actions,
138
+ isinstance(self.conversation.state.confirmation_policy, ConfirmRisky),
139
+ )
140
+ decision = result.decision
141
+ policy_change = result.policy_change
142
+
143
+ if decision == UserConfirmation.REJECT:
144
+ self.conversation.reject_pending_actions(
145
+ result.reason or 'User rejected the actions'
146
+ )
147
+ return decision
148
+
149
+ if decision == UserConfirmation.DEFER:
150
+ self.conversation.pause()
151
+ return decision
152
+
153
+ if isinstance(policy_change, NeverConfirm):
154
+ print_formatted_text(
155
+ HTML(
156
+ '<yellow>Confirmation mode disabled. Agent will proceed without asking.</yellow>'
157
+ )
158
+ )
159
+
160
+ # Remove security analyzer when policy is never confirm
161
+ self.toggle_confirmation_mode()
162
+ return decision
163
+
164
+ if isinstance(policy_change, ConfirmRisky):
165
+ print_formatted_text(
166
+ HTML(
167
+ '<yellow>Security-based confirmation enabled. '
168
+ 'LOW/MEDIUM risk actions will auto-confirm, HIGH risk actions will ask for confirmation.</yellow>'
169
+ )
170
+ )
171
+
172
+ # Keep security analyzer, change existing policy
173
+ self.set_confirmation_policy(policy_change)
174
+ return decision
175
+
176
+ # Accept action without changing existing policies
177
+ assert decision == UserConfirmation.ACCEPT
178
+ return decision
openhands_cli/setup.py ADDED
@@ -0,0 +1,116 @@
1
+ import uuid
2
+
3
+ from prompt_toolkit import HTML, print_formatted_text
4
+
5
+ from openhands.sdk import BaseConversation, Conversation, Workspace, register_tool
6
+ from openhands.tools.execute_bash import BashTool
7
+ from openhands.tools.file_editor import FileEditorTool
8
+ from openhands.tools.task_tracker import TaskTrackerTool
9
+ from openhands_cli.listeners import LoadingContext
10
+ from openhands_cli.locations import CONVERSATIONS_DIR, WORK_DIR
11
+ from openhands_cli.tui.settings.store import AgentStore
12
+ from openhands.sdk.security.confirmation_policy import (
13
+ AlwaysConfirm,
14
+ )
15
+ from openhands_cli.tui.settings.settings_screen import SettingsScreen
16
+
17
+
18
+ register_tool('BashTool', BashTool)
19
+ register_tool('FileEditorTool', FileEditorTool)
20
+ register_tool('TaskTrackerTool', TaskTrackerTool)
21
+
22
+
23
+ class MissingAgentSpec(Exception):
24
+ """Raised when agent specification is not found or invalid."""
25
+
26
+ pass
27
+
28
+
29
+ def setup_conversation(
30
+ conversation_id: str | None = None,
31
+ include_security_analyzer: bool = True
32
+ ) -> BaseConversation:
33
+ """
34
+ Setup the conversation with agent.
35
+
36
+ Args:
37
+ conversation_id: conversation ID to use. If not provided, a random UUID will be generated.
38
+
39
+ Raises:
40
+ MissingAgentSpec: If agent specification is not found or invalid.
41
+ """
42
+
43
+ # Use provided conversation_id or generate a random one
44
+ if conversation_id is None:
45
+ conversation_id = uuid.uuid4()
46
+ elif isinstance(conversation_id, str):
47
+ try:
48
+ conversation_id = uuid.UUID(conversation_id)
49
+ except ValueError as e:
50
+ print_formatted_text(
51
+ HTML(
52
+ f"<yellow>Warning: '{conversation_id}' is not a valid UUID.</yellow>"
53
+ )
54
+ )
55
+ raise e
56
+
57
+ with LoadingContext('Initializing OpenHands agent...'):
58
+ agent_store = AgentStore()
59
+ agent = agent_store.load(session_id=str(conversation_id))
60
+ if not agent:
61
+ raise MissingAgentSpec(
62
+ 'Agent specification not found. Please configure your agent settings.'
63
+ )
64
+
65
+
66
+ if not include_security_analyzer:
67
+ # Remove security analyzer from agent spec
68
+ agent = agent.model_copy(
69
+ update={"security_analyzer": None}
70
+ )
71
+
72
+ # Create conversation - agent context is now set in AgentStore.load()
73
+ conversation: BaseConversation = Conversation(
74
+ agent=agent,
75
+ workspace=Workspace(working_dir=WORK_DIR),
76
+ # Conversation will add /<conversation_id> to this path
77
+ persistence_dir=CONVERSATIONS_DIR,
78
+ conversation_id=conversation_id,
79
+ )
80
+
81
+ if include_security_analyzer:
82
+ conversation.set_confirmation_policy(AlwaysConfirm())
83
+
84
+ print_formatted_text(
85
+ HTML(f'<green>✓ Agent initialized with model: {agent.llm.model}</green>')
86
+ )
87
+ return conversation
88
+
89
+
90
+
91
+ def start_fresh_conversation(
92
+ resume_conversation_id: str | None = None
93
+ ) -> BaseConversation:
94
+ """Start a fresh conversation by creating a new conversation instance.
95
+
96
+ Handles the complete conversation setup process including settings screen
97
+ if agent configuration is missing.
98
+
99
+ Args:
100
+ resume_conversation_id: Optional conversation ID to resume
101
+
102
+ Returns:
103
+ BaseConversation: A new conversation instance
104
+ """
105
+ conversation = None
106
+ settings_screen = SettingsScreen()
107
+ try:
108
+ conversation = setup_conversation(resume_conversation_id)
109
+ return conversation
110
+ except MissingAgentSpec:
111
+ # For first-time users, show the full settings flow with choice between basic/advanced
112
+ settings_screen.configure_settings(first_time=True)
113
+
114
+
115
+ # Try once again after settings setup attempt
116
+ return setup_conversation(resume_conversation_id)
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Simple main entry point for OpenHands CLI.
4
+ This is a simplified version that demonstrates the TUI functionality.
5
+ """
6
+
7
+ import logging
8
+ import os
9
+ import sys
10
+ import warnings
11
+
12
+ debug_env = os.getenv('DEBUG', 'false').lower()
13
+ if debug_env != '1' and debug_env != 'true':
14
+ logging.disable(logging.WARNING)
15
+ warnings.filterwarnings('ignore')
16
+
17
+ from prompt_toolkit import print_formatted_text
18
+ from prompt_toolkit.formatted_text import HTML
19
+
20
+ from openhands_cli.argparsers.main_parser import create_main_parser
21
+
22
+
23
+ def main() -> None:
24
+ """Main entry point for the OpenHands CLI.
25
+
26
+ Raises:
27
+ ImportError: If agent chat dependencies are missing
28
+ Exception: On other error conditions
29
+ """
30
+ parser = create_main_parser()
31
+ args = parser.parse_args()
32
+
33
+ try:
34
+ if args.command == 'serve':
35
+ # Import gui_launcher only when needed
36
+ from openhands_cli.gui_launcher import launch_gui_server
37
+
38
+ launch_gui_server(mount_cwd=args.mount_cwd, gpu=args.gpu)
39
+ else:
40
+ # Default CLI behavior - no subcommand needed
41
+ # Import agent_chat only when needed
42
+ from openhands_cli.agent_chat import run_cli_entry
43
+
44
+ # Start agent chat
45
+ run_cli_entry(resume_conversation_id=args.resume)
46
+ except KeyboardInterrupt:
47
+ print_formatted_text(HTML('\n<yellow>Goodbye! 👋</yellow>'))
48
+ except EOFError:
49
+ print_formatted_text(HTML('\n<yellow>Goodbye! 👋</yellow>'))
50
+ except Exception as e:
51
+ print_formatted_text(HTML(f'<red>Error: {e}</red>'))
52
+ import traceback
53
+
54
+ traceback.print_exc()
55
+ raise
56
+
57
+
58
+ if __name__ == '__main__':
59
+ main()
@@ -0,0 +1,5 @@
1
+ from openhands_cli.tui.tui import DEFAULT_STYLE
2
+
3
+ __all__ = [
4
+ 'DEFAULT_STYLE',
5
+ ]