Skip to content

Commit a9e4178

Browse files
committed
Improved CLI
1 parent 9d25164 commit a9e4178

File tree

11 files changed

+1051
-233
lines changed

11 files changed

+1051
-233
lines changed

interpreter_1/cli copy.py

Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
import sys
2+
3+
if len(sys.argv) > 1 and sys.argv[1] == "--help":
4+
from .misc.help import help_message
5+
6+
help_message()
7+
sys.exit(0)
8+
9+
if len(sys.argv) > 1 and sys.argv[1] == "--version":
10+
# Print version of currently installed interpreter
11+
# Get this from the package metadata
12+
from importlib.metadata import version
13+
14+
print("Open Interpreter " + version("open-interpreter"))
15+
sys.exit(0)
16+
17+
import argparse
18+
import asyncio
19+
import os
20+
from concurrent.futures import ThreadPoolExecutor
21+
from typing import Any, Dict
22+
23+
from .misc.spinner import SimpleSpinner
24+
from .profiles import Profile
25+
26+
27+
def _parse_list_arg(value: str) -> list:
28+
"""Parse a comma-separated or JSON-formatted string into a list"""
29+
if not value:
30+
return []
31+
32+
# Try parsing as JSON first
33+
if value.startswith("["):
34+
try:
35+
import json
36+
37+
return json.loads(value)
38+
except json.JSONDecodeError:
39+
pass
40+
41+
# Fall back to comma-separated parsing
42+
return [item.strip() for item in value.split(",") if item.strip()]
43+
44+
45+
def _profile_to_arg_params(profile: Profile) -> Dict[str, Dict[str, Any]]:
46+
"""Convert Profile attributes to argparse parameter definitions"""
47+
return {
48+
# Server configuration
49+
"server": {
50+
"flags": ["--serve", "-s"],
51+
"action": "store_true",
52+
"default": profile.serve,
53+
"help": "Start the server",
54+
},
55+
# Model and API configuration
56+
"model": {
57+
"flags": ["--model", "-m"],
58+
"default": profile.model,
59+
"help": "Specify the model name",
60+
},
61+
"provider": {
62+
"flags": ["--provider"],
63+
"default": profile.provider,
64+
"help": "Specify the API provider",
65+
},
66+
"api_base": {
67+
"flags": ["--api-base", "-b"],
68+
"default": profile.api_base,
69+
"help": "Specify the API base URL",
70+
},
71+
"api_key": {
72+
"flags": ["--api-key", "-k"],
73+
"default": profile.api_key,
74+
"help": "Specify the API key",
75+
},
76+
"api_version": {
77+
"flags": ["--api-version"],
78+
"default": profile.api_version,
79+
"help": "Specify the API version",
80+
},
81+
"temperature": {
82+
"flags": ["--temperature"],
83+
"default": profile.temperature,
84+
"help": "Specify the temperature",
85+
},
86+
"max_tokens": {
87+
"flags": ["--max-tokens"],
88+
"default": profile.max_tokens,
89+
"help": "Specify the maximum number of tokens",
90+
},
91+
# Tool configuration
92+
"tools": {
93+
"flags": ["--tools"],
94+
"default": profile.tools,
95+
"help": "Specify enabled tools (comma-separated or JSON list)",
96+
"type": _parse_list_arg,
97+
},
98+
"allowed_commands": {
99+
"flags": ["--allowed-commands"],
100+
"default": profile.allowed_commands,
101+
"help": "Specify allowed commands (comma-separated or JSON list)",
102+
"type": _parse_list_arg,
103+
},
104+
"allowed_paths": {
105+
"flags": ["--allowed-paths"],
106+
"default": profile.allowed_paths,
107+
"help": "Specify allowed paths (comma-separated or JSON list)",
108+
"type": _parse_list_arg,
109+
},
110+
"auto_run": {
111+
"flags": ["--auto-run", "-y"],
112+
"action": "store_true",
113+
"default": profile.auto_run,
114+
"help": "Automatically run tools",
115+
},
116+
"tool_calling": {
117+
"flags": ["--no-tool-calling"],
118+
"action": "store_false",
119+
"default": profile.tool_calling,
120+
"dest": "tool_calling",
121+
"help": "Disable tool calling (enabled by default)",
122+
},
123+
"interactive": {
124+
"flags": ["--interactive"],
125+
"action": "store_true",
126+
"default": profile.interactive,
127+
"help": "Enable interactive mode (enabled by default)",
128+
},
129+
"no_interactive": {
130+
"flags": ["--no-interactive"],
131+
"action": "store_false",
132+
"default": profile.interactive,
133+
"dest": "interactive",
134+
"help": "Disable interactive mode",
135+
},
136+
# Behavior configuration
137+
"system_message": {
138+
"flags": ["--system-message"],
139+
"default": profile.system_message,
140+
"help": "Overwrite system message",
141+
},
142+
"custom_instructions": {
143+
"flags": ["--instructions"],
144+
"default": profile.instructions,
145+
"help": "Appended to default system message",
146+
},
147+
"max_turns": {
148+
"flags": ["--max-turns"],
149+
"type": int,
150+
"default": profile.max_turns,
151+
"help": "Set maximum conversation turns, defaults to -1 (unlimited)",
152+
},
153+
"profile": {
154+
"flags": ["--profile"],
155+
"default": profile.profile_path,
156+
"help": "Path to profile configuration",
157+
},
158+
# Debugging
159+
"debug": {
160+
"flags": ["--debug", "-d"],
161+
"action": "store_true",
162+
"default": profile.debug,
163+
"help": "Run in debug mode",
164+
},
165+
}
166+
167+
168+
def parse_args():
169+
# Create profile with defaults
170+
profile = Profile()
171+
# Load from default location if it exists
172+
default_profile_path = os.path.expanduser(Profile.DEFAULT_PROFILE_PATH)
173+
if os.path.exists(default_profile_path):
174+
profile.load(Profile.DEFAULT_PROFILE_PATH)
175+
176+
parser = argparse.ArgumentParser(add_help=False)
177+
178+
# Hidden arguments
179+
parser.add_argument("--help", "-h", action="store_true", help=argparse.SUPPRESS)
180+
parser.add_argument("--version", action="store_true", help=argparse.SUPPRESS)
181+
parser.add_argument("--input", action="store", help=argparse.SUPPRESS)
182+
parser.add_argument(
183+
"--profiles", action="store_true", help="Open profiles directory"
184+
)
185+
186+
# Add arguments programmatically from config
187+
arg_params = _profile_to_arg_params(profile)
188+
for param in arg_params.values():
189+
flags = param.pop("flags")
190+
parser.add_argument(*flags, **param)
191+
192+
# If second argument exists and doesn't start with '-', treat as input message
193+
if len(sys.argv) > 1 and not sys.argv[1].startswith("-"):
194+
return {**vars(parser.parse_args([])), "input": "i " + " ".join(sys.argv[1:])}
195+
196+
args = vars(parser.parse_args())
197+
198+
# Handle profiles flag
199+
if args["profiles"]:
200+
profile_dir = os.path.expanduser(Profile.DEFAULT_PROFILE_FOLDER)
201+
if sys.platform == "win32":
202+
os.startfile(profile_dir)
203+
else:
204+
import subprocess
205+
206+
opener = "open" if sys.platform == "darwin" else "xdg-open"
207+
subprocess.run([opener, profile_dir])
208+
sys.exit(0)
209+
210+
# If a different profile is specified, load it
211+
if args["profile"] != profile.profile_path:
212+
profile.load(args["profile"])
213+
# Update any values that weren't explicitly set in CLI
214+
for key, value in vars(profile).items():
215+
if key in args and args[key] is None:
216+
args[key] = value
217+
218+
return args
219+
220+
221+
def main():
222+
args = parse_args()
223+
224+
def load_interpreter():
225+
global interpreter
226+
from .interpreter import Interpreter
227+
228+
interpreter = Interpreter()
229+
# Configure interpreter from args
230+
for key, value in args.items():
231+
if hasattr(interpreter, key) and value is not None:
232+
setattr(interpreter, key, value)
233+
234+
# Check if we should start the server
235+
if args["serve"]:
236+
# Load interpreter immediately for server mode
237+
load_interpreter()
238+
print("Starting server...")
239+
interpreter.server()
240+
return
241+
242+
async def async_load():
243+
# Load interpreter in background
244+
with ThreadPoolExecutor() as pool:
245+
await asyncio.get_event_loop().run_in_executor(pool, load_interpreter)
246+
247+
if args["input"] is None and sys.stdin.isatty():
248+
if sys.argv[0].endswith("interpreter"):
249+
from .misc.welcome import welcome_message
250+
251+
welcome_message(args)
252+
print("\n> ", end="", flush=True)
253+
try:
254+
asyncio.run(async_load())
255+
message = input()
256+
except KeyboardInterrupt:
257+
return
258+
print()
259+
interpreter.messages = [{"role": "user", "content": message}]
260+
# Run the generator until completion
261+
for _ in interpreter.respond():
262+
pass
263+
print()
264+
interpreter.chat()
265+
else:
266+
print()
267+
spinner = SimpleSpinner("")
268+
spinner.start()
269+
load_interpreter()
270+
spinner.stop()
271+
272+
if args["input"] is not None:
273+
message = args["input"]
274+
else:
275+
message = sys.stdin.read().strip()
276+
interpreter.messages = [{"role": "user", "content": message}]
277+
278+
# Run the generator until completion
279+
for _ in interpreter.respond():
280+
pass
281+
print()
282+
283+
if interpreter.interactive:
284+
interpreter.chat() # Continue in interactive mode
285+
286+
287+
if __name__ == "__main__":
288+
main()

0 commit comments

Comments
 (0)