diff --git a/interpreter_1/cli copy.py b/interpreter_1/cli copy.py new file mode 100644 index 000000000..b2c980c69 --- /dev/null +++ b/interpreter_1/cli copy.py @@ -0,0 +1,288 @@ +import sys + +if len(sys.argv) > 1 and sys.argv[1] == "--help": + from .misc.help import help_message + + help_message() + sys.exit(0) + +if len(sys.argv) > 1 and sys.argv[1] == "--version": + # Print version of currently installed interpreter + # Get this from the package metadata + from importlib.metadata import version + + print("Open Interpreter " + version("open-interpreter")) + sys.exit(0) + +import argparse +import asyncio +import os +from concurrent.futures import ThreadPoolExecutor +from typing import Any, Dict + +from .misc.spinner import SimpleSpinner +from .profiles import Profile + + +def _parse_list_arg(value: str) -> list: + """Parse a comma-separated or JSON-formatted string into a list""" + if not value: + return [] + + # Try parsing as JSON first + if value.startswith("["): + try: + import json + + return json.loads(value) + except json.JSONDecodeError: + pass + + # Fall back to comma-separated parsing + return [item.strip() for item in value.split(",") if item.strip()] + + +def _profile_to_arg_params(profile: Profile) -> Dict[str, Dict[str, Any]]: + """Convert Profile attributes to argparse parameter definitions""" + return { + # Server configuration + "server": { + "flags": ["--serve", "-s"], + "action": "store_true", + "default": profile.serve, + "help": "Start the server", + }, + # Model and API configuration + "model": { + "flags": ["--model", "-m"], + "default": profile.model, + "help": "Specify the model name", + }, + "provider": { + "flags": ["--provider"], + "default": profile.provider, + "help": "Specify the API provider", + }, + "api_base": { + "flags": ["--api-base", "-b"], + "default": profile.api_base, + "help": "Specify the API base URL", + }, + "api_key": { + "flags": ["--api-key", "-k"], + "default": profile.api_key, + "help": "Specify the API key", + }, + "api_version": { + "flags": ["--api-version"], + "default": profile.api_version, + "help": "Specify the API version", + }, + "temperature": { + "flags": ["--temperature"], + "default": profile.temperature, + "help": "Specify the temperature", + }, + "max_tokens": { + "flags": ["--max-tokens"], + "default": profile.max_tokens, + "help": "Specify the maximum number of tokens", + }, + # Tool configuration + "tools": { + "flags": ["--tools"], + "default": profile.tools, + "help": "Specify enabled tools (comma-separated or JSON list)", + "type": _parse_list_arg, + }, + "allowed_commands": { + "flags": ["--allowed-commands"], + "default": profile.allowed_commands, + "help": "Specify allowed commands (comma-separated or JSON list)", + "type": _parse_list_arg, + }, + "allowed_paths": { + "flags": ["--allowed-paths"], + "default": profile.allowed_paths, + "help": "Specify allowed paths (comma-separated or JSON list)", + "type": _parse_list_arg, + }, + "auto_run": { + "flags": ["--auto-run", "-y"], + "action": "store_true", + "default": profile.auto_run, + "help": "Automatically run tools", + }, + "tool_calling": { + "flags": ["--no-tool-calling"], + "action": "store_false", + "default": profile.tool_calling, + "dest": "tool_calling", + "help": "Disable tool calling (enabled by default)", + }, + "interactive": { + "flags": ["--interactive"], + "action": "store_true", + "default": profile.interactive, + "help": "Enable interactive mode (enabled by default)", + }, + "no_interactive": { + "flags": ["--no-interactive"], + "action": "store_false", + "default": profile.interactive, + "dest": "interactive", + "help": "Disable interactive mode", + }, + # Behavior configuration + "system_message": { + "flags": ["--system-message"], + "default": profile.system_message, + "help": "Overwrite system message", + }, + "custom_instructions": { + "flags": ["--instructions"], + "default": profile.instructions, + "help": "Appended to default system message", + }, + "max_turns": { + "flags": ["--max-turns"], + "type": int, + "default": profile.max_turns, + "help": "Set maximum conversation turns, defaults to -1 (unlimited)", + }, + "profile": { + "flags": ["--profile"], + "default": profile.profile_path, + "help": "Path to profile configuration", + }, + # Debugging + "debug": { + "flags": ["--debug", "-d"], + "action": "store_true", + "default": profile.debug, + "help": "Run in debug mode", + }, + } + + +def parse_args(): + # Create profile with defaults + profile = Profile() + # Load from default location if it exists + default_profile_path = os.path.expanduser(Profile.DEFAULT_PROFILE_PATH) + if os.path.exists(default_profile_path): + profile.load(Profile.DEFAULT_PROFILE_PATH) + + parser = argparse.ArgumentParser(add_help=False) + + # Hidden arguments + parser.add_argument("--help", "-h", action="store_true", help=argparse.SUPPRESS) + parser.add_argument("--version", action="store_true", help=argparse.SUPPRESS) + parser.add_argument("--input", action="store", help=argparse.SUPPRESS) + parser.add_argument( + "--profiles", action="store_true", help="Open profiles directory" + ) + + # Add arguments programmatically from config + arg_params = _profile_to_arg_params(profile) + for param in arg_params.values(): + flags = param.pop("flags") + parser.add_argument(*flags, **param) + + # If second argument exists and doesn't start with '-', treat as input message + if len(sys.argv) > 1 and not sys.argv[1].startswith("-"): + return {**vars(parser.parse_args([])), "input": "i " + " ".join(sys.argv[1:])} + + args = vars(parser.parse_args()) + + # Handle profiles flag + if args["profiles"]: + profile_dir = os.path.expanduser(Profile.DEFAULT_PROFILE_FOLDER) + if sys.platform == "win32": + os.startfile(profile_dir) + else: + import subprocess + + opener = "open" if sys.platform == "darwin" else "xdg-open" + subprocess.run([opener, profile_dir]) + sys.exit(0) + + # If a different profile is specified, load it + if args["profile"] != profile.profile_path: + profile.load(args["profile"]) + # Update any values that weren't explicitly set in CLI + for key, value in vars(profile).items(): + if key in args and args[key] is None: + args[key] = value + + return args + + +def main(): + args = parse_args() + + def load_interpreter(): + global interpreter + from .interpreter import Interpreter + + interpreter = Interpreter() + # Configure interpreter from args + for key, value in args.items(): + if hasattr(interpreter, key) and value is not None: + setattr(interpreter, key, value) + + # Check if we should start the server + if args["serve"]: + # Load interpreter immediately for server mode + load_interpreter() + print("Starting server...") + interpreter.server() + return + + async def async_load(): + # Load interpreter in background + with ThreadPoolExecutor() as pool: + await asyncio.get_event_loop().run_in_executor(pool, load_interpreter) + + if args["input"] is None and sys.stdin.isatty(): + if sys.argv[0].endswith("interpreter"): + from .misc.welcome import welcome_message + + welcome_message(args) + print("\n> ", end="", flush=True) + try: + asyncio.run(async_load()) + message = input() + except KeyboardInterrupt: + return + print() + interpreter.messages = [{"role": "user", "content": message}] + # Run the generator until completion + for _ in interpreter.respond(): + pass + print() + interpreter.chat() + else: + print() + spinner = SimpleSpinner("") + spinner.start() + load_interpreter() + spinner.stop() + + if args["input"] is not None: + message = args["input"] + else: + message = sys.stdin.read().strip() + interpreter.messages = [{"role": "user", "content": message}] + + # Run the generator until completion + for _ in interpreter.respond(): + pass + print() + + if interpreter.interactive: + interpreter.chat() # Continue in interactive mode + + +if __name__ == "__main__": + main() diff --git a/interpreter_1/cli.py b/interpreter_1/cli.py index b52d15f69..11de43066 100644 --- a/interpreter_1/cli.py +++ b/interpreter_1/cli.py @@ -1,13 +1,31 @@ +import sys + +# Help message +if "--help" in sys.argv: + from .misc.help import help_message + + help_message() + sys.exit(0) + +# Version message +if "--version" in sys.argv: + print("Open Interpreter 1.0.0") + sys.exit(0) + import argparse import asyncio import os -import sys -from concurrent.futures import ThreadPoolExecutor +import subprocess +import threading from typing import Any, Dict +from .misc.get_input import get_input from .misc.spinner import SimpleSpinner from .profiles import Profile +# Global interpreter object +global_interpreter = None + def _parse_list_arg(value: str) -> list: """Parse a comma-separated or JSON-formatted string into a list""" @@ -30,14 +48,12 @@ def _parse_list_arg(value: str) -> list: def _profile_to_arg_params(profile: Profile) -> Dict[str, Dict[str, Any]]: """Convert Profile attributes to argparse parameter definitions""" return { - # Server configuration "server": { "flags": ["--serve", "-s"], "action": "store_true", "default": profile.serve, "help": "Start the server", }, - # Model and API configuration "model": { "flags": ["--model", "-m"], "default": profile.model, @@ -73,7 +89,6 @@ def _profile_to_arg_params(profile: Profile) -> Dict[str, Dict[str, Any]]: "default": profile.max_tokens, "help": "Specify the maximum number of tokens", }, - # Tool configuration "tools": { "flags": ["--tools"], "default": profile.tools, @@ -118,7 +133,6 @@ def _profile_to_arg_params(profile: Profile) -> Dict[str, Dict[str, Any]]: "dest": "interactive", "help": "Disable interactive mode", }, - # Behavior configuration "system_message": { "flags": ["--system-message"], "default": profile.system_message, @@ -140,7 +154,6 @@ def _profile_to_arg_params(profile: Profile) -> Dict[str, Dict[str, Any]]: "default": profile.profile_path, "help": "Path to profile configuration", }, - # Debugging "debug": { "flags": ["--debug", "-d"], "action": "store_true", @@ -150,17 +163,78 @@ def _profile_to_arg_params(profile: Profile) -> Dict[str, Dict[str, Any]]: } +def load_interpreter(args): + from .interpreter import Interpreter + + interpreter = Interpreter() + for key, value in args.items(): + if hasattr(interpreter, key) and value is not None: + setattr(interpreter, key, value) + return interpreter + + +async def async_load_interpreter(args): + return load_interpreter(args) + + +async def async_main(args): + global global_interpreter + + if args["serve"]: + global_interpreter = await async_load_interpreter(args) + print("Starting server...") + global_interpreter.server() + return + + if ( + args["input"] is None + and sys.stdin.isatty() + and sys.argv[0].endswith("interpreter") + ): + from .misc.welcome import welcome_message + + welcome_message() + + if args["input"] is None and ( + sys.stdin.isatty() and args.get("no_interactive") is not True + ): + # Load the interpreter in a separate thread + def load_interpreter_thread(args): + loop = asyncio.new_event_loop() + global global_interpreter + global_interpreter = loop.run_until_complete(async_load_interpreter(args)) + + thread = threading.Thread(target=load_interpreter_thread, args=(args,)) + thread.start() + + # Get user input + message = await get_input() + + # Wait for the thread to finish + thread.join() + else: + spinner = SimpleSpinner() + spinner.start() + global_interpreter = await async_load_interpreter(args) + message = args["input"] if args["input"] is not None else sys.stdin.read() + spinner.stop() + print() + global_interpreter.messages = [{"role": "user", "content": message}] + async for _ in global_interpreter.async_respond(): + pass + print() + + return global_interpreter + + def parse_args(): - # Create profile with defaults profile = Profile() - # Load from default location if it exists default_profile_path = os.path.expanduser(Profile.DEFAULT_PROFILE_PATH) if os.path.exists(default_profile_path): profile.load(Profile.DEFAULT_PROFILE_PATH) parser = argparse.ArgumentParser(add_help=False) - # Hidden arguments parser.add_argument("--help", "-h", action="store_true", help=argparse.SUPPRESS) parser.add_argument("--version", action="store_true", help=argparse.SUPPRESS) parser.add_argument("--input", action="store", help=argparse.SUPPRESS) @@ -168,34 +242,27 @@ def parse_args(): "--profiles", action="store_true", help="Open profiles directory" ) - # Add arguments programmatically from config arg_params = _profile_to_arg_params(profile) for param in arg_params.values(): flags = param.pop("flags") parser.add_argument(*flags, **param) - # If second argument exists and doesn't start with '-', treat as input message if len(sys.argv) > 1 and not sys.argv[1].startswith("-"): return {**vars(parser.parse_args([])), "input": "i " + " ".join(sys.argv[1:])} args = vars(parser.parse_args()) - # Handle profiles flag if args["profiles"]: profile_dir = os.path.expanduser(Profile.DEFAULT_PROFILE_FOLDER) if sys.platform == "win32": os.startfile(profile_dir) else: - import subprocess - opener = "open" if sys.platform == "darwin" else "xdg-open" subprocess.run([opener, profile_dir]) sys.exit(0) - # If a different profile is specified, load it if args["profile"] != profile.profile_path: profile.load(args["profile"]) - # Update any values that weren't explicitly set in CLI for key, value in vars(profile).items(): if key in args and args[key] is None: args[key] = value @@ -204,83 +271,29 @@ def parse_args(): def main(): - args = parse_args() - - def load_interpreter(): - global interpreter - from .interpreter import Interpreter - - interpreter = Interpreter() - # Configure interpreter from args - for key, value in args.items(): - if hasattr(interpreter, key) and value is not None: - setattr(interpreter, key, value) - - if args["help"]: - from .misc.help import help_message - - help_message() + """Entry point for the CLI""" + try: + args = parse_args() + + if args["serve"]: + print("Starting OpenAI-compatible server...") + global_interpreter = load_interpreter(args) + global_interpreter.server() + return + + # Run async portion + interpreter = asyncio.run(async_main(args)) + # If we got an interpreter back and it's interactive, start chat in sync context + if interpreter and interpreter.interactive: + interpreter.chat() + + except KeyboardInterrupt: + print("KeyboardInterrupt") sys.exit(0) - - if args["version"]: - # Print version of currently installed interpreter - # Get this from the package metadata - from importlib.metadata import version - - print("Open Interpreter " + version("open-interpreter")) + except asyncio.CancelledError: + print("CancelledError") sys.exit(0) - # Check if we should start the server - if args["serve"]: - # Load interpreter immediately for server mode - load_interpreter() - print("Starting server...") - interpreter.server() - return - - async def async_load(): - # Write initial prompt with placeholder - sys.stdout.write( - '\n> Use """ for multi-line prompts' - ) # Write prompt and placeholder - sys.stdout.write("\r> ") # Move cursor back to after > - sys.stdout.flush() - - # Load interpreter in background - with ThreadPoolExecutor() as pool: - await asyncio.get_event_loop().run_in_executor(pool, load_interpreter) - - # Clear the line when done - sys.stdout.write("\r\033[K") # Clear entire line - sys.stdout.flush() - - if args["input"] is None and sys.stdin.isatty(): - from .misc.welcome import welcome_message - - welcome_message(args) - asyncio.run(async_load()) - interpreter.chat() - else: - print() - spinner = SimpleSpinner("") - spinner.start() - load_interpreter() - spinner.stop() - - if args["input"] is not None: - message = args["input"] - else: - message = sys.stdin.read().strip() - interpreter.messages = [{"role": "user", "content": message}] - - # Run the generator until completion - for _ in interpreter.respond(): - pass - print() - - if interpreter.interactive: - interpreter.chat() # Continue in interactive mode - if __name__ == "__main__": main() diff --git a/interpreter_1/interpreter.py b/interpreter_1/interpreter.py index cbeb7812e..4ab4e3bf1 100644 --- a/interpreter_1/interpreter.py +++ b/interpreter_1/interpreter.py @@ -10,13 +10,9 @@ from typing import Any, cast from prompt_toolkit import PromptSession -from prompt_toolkit.formatted_text import HTML from readchar import readchar -try: - from enum import StrEnum -except ImportError: # Python 3.10 compatibility - from enum import Enum as StrEnum +from .misc.get_input import get_input # Third-party imports os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" @@ -148,7 +144,6 @@ def __init__(self, profile=None): self._client = None self._spinner = SimpleSpinner("") - self._prompt_session = None self._command_handler = CommandHandler(self) self._stop_flag = False # Add stop flag @@ -316,7 +311,7 @@ async def async_respond(self): self._client = Anthropic() if self.debug: - print("\nSending messages:", self.messages, "\n") + print("Sending messages:", self.messages, "\n") # Use Anthropic API which supports betas raw_response = self._client.beta.messages.create( @@ -851,54 +846,10 @@ def chat(self): Interactive mode """ try: - placeholder_color = "ansigray" - message_count = 0 while True: - # Determine placeholder text based on message count - if message_count in [0, 1]: - placeholder_text = 'Use """ for multi-line prompts' - elif message_count in []: # Disabled - placeholder_text = "Type /help for advanced commands" - else: - placeholder_text = "" - - # Get first line of input with placeholder - placeholder = HTML( - f"<{placeholder_color}>{placeholder_text}" - ) - if self._prompt_session is None: - self._prompt_session = PromptSession() - - try: - # Prompt toolkit requires terminal size to work properly - # If this fails, prompt toolkit will look weird, so we fall back to standard input - os.get_terminal_size() - user_input = self._prompt_session.prompt( - "> ", - placeholder=placeholder, - ).strip() - except KeyboardInterrupt: - raise - except: - user_input = input("> ").strip() - print() - - # Handle multi-line input - if user_input == '"""': - user_input = "" - print('> """') - while True: - placeholder = HTML( - f'<{placeholder_color}>Use """ again to finish' - ) - line = self._prompt_session.prompt( - "", placeholder=placeholder - ).strip() - if line == '"""': - break - user_input += line + "\n" - print() + user_input = input("> ") + print("") message_count += 1 # Increment counter after each message diff --git a/interpreter_1/misc/get_input copy.py b/interpreter_1/misc/get_input copy.py new file mode 100644 index 000000000..9dafdb53e --- /dev/null +++ b/interpreter_1/misc/get_input copy.py @@ -0,0 +1,77 @@ +import asyncio +import fcntl +import os +import sys +import termios + + +async def get_input( + placeholder_text: str = "Testing", placeholder_color: str = "gray" +) -> str: + # Save terminal settings and set raw mode + old_settings = termios.tcgetattr(sys.stdin.fileno()) + tty_settings = termios.tcgetattr(sys.stdin.fileno()) + tty_settings[3] = tty_settings[3] & ~(termios.ECHO | termios.ICANON) + termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, tty_settings) + + # Set up non-blocking stdin + fd = sys.stdin.fileno() + flags = fcntl.fcntl(fd, fcntl.F_GETFL) + fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK) + + COLORS = { + "gray": "\033[90m", + "red": "\033[91m", + "green": "\033[92m", + "yellow": "\033[93m", + "blue": "\033[94m", + "magenta": "\033[95m", + "cyan": "\033[96m", + "white": "\033[97m", + } + RESET = "\033[0m" + + current_input = [] + show_placeholder = True + + def redraw(): + sys.stdout.write("\r\033[K") # Clear line + sys.stdout.write("\r> ") + if current_input: + sys.stdout.write("".join(current_input)) + elif show_placeholder: + color_code = COLORS.get(placeholder_color.lower(), COLORS["gray"]) + sys.stdout.write(f"{color_code}{placeholder_text}{RESET}") + sys.stdout.write("\r> ") + sys.stdout.flush() + + try: + redraw() + while True: + try: + char = os.read(fd, 1).decode() + + if char == "\n": + if current_input: + result = "".join(current_input) + return result + else: + redraw() + elif char == "\x7f": # Backspace + if current_input: + current_input.pop() + if not current_input: + show_placeholder = True + elif char == "\x03": # Ctrl+C + raise KeyboardInterrupt + elif char and char.isprintable(): + current_input.append(char) + show_placeholder = False + redraw() + except BlockingIOError: + pass + + finally: + termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, old_settings) + fcntl.fcntl(fd, fcntl.F_SETFL, flags) + print() diff --git a/interpreter_1/misc/get_input.py b/interpreter_1/misc/get_input.py new file mode 100644 index 000000000..9784335f6 --- /dev/null +++ b/interpreter_1/misc/get_input.py @@ -0,0 +1,97 @@ +import asyncio +import fcntl +import os +import random +import sys +import termios + + +async def get_input(placeholder_text=None, placeholder_color: str = "gray") -> str: + if placeholder_text is None: + common_placeholders = [ + "How can I help you?", + ] + rare_placeholders = [ + 'Use """ for multi-line input', + "Psst... try the wtf command", + ] + very_rare_placeholders = ["Let's make history together!"] + + # 69% common, 30% rare, 1% very rare + rand = random.random() + if rand < 0.69: + placeholder_text = random.choice(common_placeholders) + elif rand < 0.99: + placeholder_text = random.choice(rare_placeholders) + else: + placeholder_text = random.choice(very_rare_placeholders) + + placeholder_text = "Describe command" + + # Save terminal settings and set raw mode + old_settings = termios.tcgetattr(sys.stdin.fileno()) + tty_settings = termios.tcgetattr(sys.stdin.fileno()) + tty_settings[3] = tty_settings[3] & ~(termios.ECHO | termios.ICANON) + termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, tty_settings) + + # Set up non-blocking stdin + fd = sys.stdin.fileno() + flags = fcntl.fcntl(fd, fcntl.F_GETFL) + fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK) + + COLORS = { + "gray": "\033[90m", + "red": "\033[91m", + "green": "\033[92m", + "yellow": "\033[93m", + "blue": "\033[94m", + "magenta": "\033[95m", + "cyan": "\033[96m", + "white": "\033[97m", + } + RESET = "\033[0m" + + current_input = [] + show_placeholder = True + + def redraw(): + sys.stdout.write("\r\033[K") # Clear line + sys.stdout.write("\r> ") + if current_input: + sys.stdout.write("".join(current_input)) + elif show_placeholder: + color_code = COLORS.get(placeholder_color.lower(), COLORS["gray"]) + sys.stdout.write(f"{color_code}{placeholder_text}{RESET}") + sys.stdout.write("\r> ") + sys.stdout.flush() + + try: + redraw() + while True: + try: + char = os.read(fd, 1).decode() + + if char == "\n": + if current_input: + result = "".join(current_input) + return result + else: + redraw() + elif char == "\x7f": # Backspace + if current_input: + current_input.pop() + if not current_input: + show_placeholder = True + elif char == "\x03": # Ctrl+C + raise KeyboardInterrupt + elif char and char.isprintable(): + current_input.append(char) + show_placeholder = False + redraw() + except BlockingIOError: + pass + + finally: + termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, old_settings) + fcntl.fcntl(fd, fcntl.F_SETFL, flags) + print() diff --git a/interpreter_1/misc/help.py b/interpreter_1/misc/help.py index fa1141f54..5ff267779 100644 --- a/interpreter_1/misc/help.py +++ b/interpreter_1/misc/help.py @@ -1,8 +1,3 @@ -import json - -from ..ui.tool import ToolRenderer - - def help_message(): tips = [ "\033[38;5;240mTip: Pipe in prompts using `$ANYTHING | i`\033[0m", @@ -12,9 +7,11 @@ def help_message(): BLUE_COLOR = "\033[94m" RESET_COLOR = "\033[0m" - content = f""" -A standard interface for computer-controlling agents. + content = f"""Open Interpreter 1.0.0 +Copyright (C) 2024 Open Interpreter Team +Licensed under GNU AGPL v3.0 +A modern command-line assistant. \033[1mUSAGE\033[0m @@ -46,25 +43,25 @@ def help_message(): {BLUE_COLOR}--profile{RESET_COLOR} Load settings from config file {BLUE_COLOR}--profiles{RESET_COLOR} Open profiles directory {BLUE_COLOR}--serve{RESET_COLOR} Start OpenAI-compatible server - - """ # Add an indent to each line # content = "\n".join(f" {line}" for line in content.split("\n")) - string = json.dumps( - {"command": "Open Interpreter", "path": "", "file_text": content} - ) + # string = json.dumps( + # {"command": "Open Interpreter", "path": "", "file_text": content} + # ) - renderer = ToolRenderer(name="str_replace_editor") + # renderer = ToolRenderer(name="str_replace_editor") # for chunk in stream_text(string, min_delay=0.00001, max_delay=0.0001, max_chunk=50): # renderer.feed(chunk) - renderer.feed(string) + # renderer.feed(string) - renderer.close() + # renderer.close() + + print(content) # time.sleep(0.03) print("") @@ -72,3 +69,45 @@ def help_message(): # print("\033[38;5;238mA.C., 2024. https://openinterpreter.com/\033[0m\n") print("\033[38;5;238mhttps://docs.openinterpreter.com/\033[0m\n") # time.sleep(0.05) + + +def help_message(): + print( + """ +usage: interpreter [flags] + i [prompt] + +A modern command-line assistant. + +flags: + --model model to use for completion + --provider api provider (e.g. openai, anthropic) + --api-base base url for api requests + --api-key api key for authentication + --api-version api version to use + --temperature sampling temperature (default: 0) + + --tools comma-separated tools: interpreter,editor,gui + --allowed-commands commands the model can execute + --allowed-paths paths the model can access + --no-tool-calling disable tool calling (instead parse markdown code) + --auto-run, -y auto-run suggested commands + --interactive force interactive mode (true if sys.stdin.isatty()) + --no-interactive disable interactive mode + + --instructions additional instructions in system message + --input pre-fill first user message + --system-message override default system message + --max-turns maximum conversation turns (-1 for unlimited) + + --profile load settings from config file + --profiles open profiles directory + --serve start openai-compatible server + +example: i want a venv here +example: interpreter --model ollama/llama3.2 --serve +example: i -y --input "run pytest, fix errors" +example: cat instructions.txt | i +example: i --tools gui "try to earn $1" + """ + ) diff --git a/interpreter_1/misc/user_input copy.py b/interpreter_1/misc/user_input copy.py new file mode 100644 index 000000000..f649bb348 --- /dev/null +++ b/interpreter_1/misc/user_input copy.py @@ -0,0 +1,55 @@ +# from prompt_toolkit import PromptSession +# from prompt_toolkit.formatted_text import HTML +# import os + + +def get_user_input( + placeholder_text: str = "", placeholder_color: str = "ansigray", prompt_session=None +) -> str: + """ + Get user input with support for multi-line input and fallback to standard input. + + Args: + placeholder_text: Text to show as placeholder + placeholder_color: Color of the placeholder text + prompt_session: Optional PromptSession instance to use + + Returns: + The user's input as a string + """ + return input("> ") + # Create placeholder HTML + placeholder = HTML(f"<{placeholder_color}>{placeholder_text}") + + # Use provided prompt session or create new one + if prompt_session is None: + prompt_session = PromptSession() + + try: + # Prompt toolkit requires terminal size to work properly + # If this fails, prompt toolkit will look weird, so we fall back to standard input + os.get_terminal_size() + user_input = prompt_session.prompt( + "> ", + placeholder=placeholder, + ).strip() + except KeyboardInterrupt: + raise + except: + user_input = input("> ").strip() + print() + + # Handle multi-line input + if user_input == '"""': + user_input = "" + while True: + placeholder = HTML( + f'<{placeholder_color}>Use """ again to finish' + ) + line = prompt_session.prompt("", placeholder=placeholder).strip() + if line == '"""': + break + user_input += line + "\n" + print() + + return user_input diff --git a/interpreter_1/misc/welcome copy.py b/interpreter_1/misc/welcome copy.py new file mode 100644 index 000000000..694aaa08c --- /dev/null +++ b/interpreter_1/misc/welcome copy.py @@ -0,0 +1,94 @@ +import os +import random +import time + +from ..ui.markdown import MarkdownRenderer +from .stream_text import stream_text + + +def welcome_message(args): + try: + terminal_width = os.get_terminal_size().columns + except: + terminal_width = 80 + print() + renderer = MarkdownRenderer() + + import random + + tips = [ + # "You can type `i` in your terminal to use Open Interpreter.", + "**Tip:** Type `wtf` in your terminal to instantly fix the last error.", + # "**Tip:** Type `wtf` in your terminal to have Open Interpreter fix the last error.", + '**Tip:** You can paste content into Open Interpreter by typing `"""` first.', + # "**Tip:** Type prompts after `i` in your terminal, for example, `i want deno`.", + "**Tip:** You can type `i [your prompt]` directly into your terminal, e.g. `i want a venv`.", # \n\nThese are all valid commands: `i want deno`, `i dont understand`, `i want a venv`", + # "**Tip:** Type your prompt directly into your CLI by starting with `i `, like `i want node`.", # \n\nThese are all valid commands: `i want deno`, `i dont understand`, `i want a venv`", + # "Our desktop app provides the best experience. Type `d` for early access.", + # "**Tip:** Reduce display resolution for better performance.", + ] + + random_tip = random.choice(tips) + + model = args["model"] + + if model == "claude-3-5-sonnet-20241022": + model = "CLAUDE-3.5-SONNET" + + model = f"` ✳ {model.upper()} `" # {"-" * (terminal_width - len(model))} # ⎇ + + if args["tool_calling"] == False: + args["tools"] = ["interpreter"] + + tool_displays = [] + for tool in ["interpreter", "editor", "gui"]: + if args["tools"] and tool in args["tools"]: + if tool == "interpreter": + tool_displays.append("` ❯ INTERPRETER `") + elif tool == "editor": + tool_displays.append("` ❚ FILE EDITOR `") + elif tool == "gui": + tool_displays.append("` ✳ GUI CONTROL `") + else: + if tool == "interpreter": + tool_displays.append(" " * len(" ❯ INTERPRETER ")) + elif tool == "editor": + tool_displays.append(" " * len(" ❚ FILE EDITOR ")) + elif tool == "gui": + tool_displays.append(" " * len(" ✳ GUI CONTROL ")) + + # Sort tool_displays so that empty tools are at the end + tool_displays = sorted( + tool_displays, key=lambda x: x == " " * len(" ❯ INTERPRETER ") + ) + + auto_run_display = ( + "` ! AUTOMATIC (UNSAFE) `" if args["auto_run"] else "` ? REQUIRES PERMISSION `" + ) + + gap = 8 + + markdown_text = f"""**MODEL**{" "*(len(model)-2+gap-len("MODEL"))}**TOOLS** +{model}{" "*gap}{tool_displays[0]} +**TOOL EXECUTION**{" "*(len(model)-2+gap-len("TOOL EXECUTION"))}{tool_displays[1]} +{auto_run_display}{" "*(len(model)+gap-len(auto_run_display))}{tool_displays[2]} + +{random_tip} + +""" + + """ + **Warning:** This AI has full system access and can modify files, install software, and execute commands. By continuing, you accept all risks and responsibility. + + Move your mouse to any corner of the screen to exit. + """ + + # for chunk in stream_text(markdown_text, max_chunk=1, min_delay=0.0001, max_delay=0.001): + # renderer.feed(chunk) + + renderer.feed(markdown_text) + + renderer.close() + + +# ⧳ ❚ ❯ ✦ ⬤ ● ▶ ⚈ ⌖ ⎋ ⬤ ◉ ⎇ diff --git a/interpreter_1/misc/welcome.py b/interpreter_1/misc/welcome.py index a6c8a65d8..2980aad32 100644 --- a/interpreter_1/misc/welcome.py +++ b/interpreter_1/misc/welcome.py @@ -1,102 +1,306 @@ import os import random -import time -from ..ui.markdown import MarkdownRenderer -from .stream_text import stream_text + +def welcome_message(args): + print( + f""" +\033[7m ✳ CLAUDE 3.5 SONNET \033[0m + +This AI can modify files, install software, and execute commands. + +By continuing, you accept all risks and responsibility. +""" + ) + + +def welcome_message(args): + print( + f''' +\033[7m ✳ CLAUDE 3.5 SONNET \033[0m + +Tip: You can paste content by typing """ first. +''' + ) + + +def welcome_message(args): + print( + f""" +\033[94m✳ claude 3.5 sonnet +❯ runs code, ≣ edits files +⌘ requires approval\033[0m +""" + ) # , ⌖ controls gui def welcome_message(args): - try: - terminal_width = os.get_terminal_size().columns - except: - terminal_width = 80 - print() - renderer = MarkdownRenderer() + # Define color combinations + COLORS = { + "medium_blue": ("\033[48;5;27m", "\033[38;5;27m"), + #'bright_blue': ("\033[48;5;33m", "\033[38;5;33m"), + "dark_blue": ("\033[48;5;20m", "\033[38;5;20m"), + "light_blue": ("\033[48;5;39m", "\033[38;5;39m"), + #'sky_blue': ("\033[48;5;117m", "\033[38;5;117m"), + #'steel_blue': ("\033[48;5;67m", "\033[38;5;67m"), + #'powder_blue': ("\033[48;5;153m", "\033[38;5;153m"), + #'royal_blue': ("\033[48;5;62m", "\033[38;5;62m"), + #'navy_blue': ("\033[48;5;17m", "\033[38;5;17m"), + #'azure_blue': ("\033[48;5;111m", "\033[38;5;111m"), + #'cornflower_blue': ("\033[48;5;69m", "\033[38;5;69m"), + "deep_sky_blue": ("\033[48;5;32m", "\033[38;5;32m"), + "dodger_blue": ("\033[48;5;33m", "\033[38;5;33m"), + #'sapphire_blue': ("\033[48;5;25m", "\033[38;5;25m") + # 'dark_gray': ("\033[48;5;240m", "\033[38;5;240m"), + # 'light_gray': ("\033[48;5;248m", "\033[38;5;248m"), + # 'white': ("\033[48;5;231m", "\033[38;5;231m"), + # 'black': ("\033[48;5;232m", "\033[38;5;232m"), + } + + WHITE_FG = "\033[97m" + BLACK_FG = "\033[30m" + RESET = "\033[0m" + + # Different text layouts + LAYOUTS = { + "basic_background": lambda bg, fg: f""" +{bg} * CLAUDE 3.5 SONNET {RESET} + +{fg}❯{RESET} runs code +{fg}≣{RESET} edits files +{fg}⌘{RESET} requires approval +""", + "compact": lambda bg, fg: f""" +{bg} * CLAUDE 3.5 SONNET {RESET} {fg}❯{RESET} code {fg}≣{RESET} files {fg}⌘{RESET} approval +""", + "white_on_color": lambda bg, fg: f""" +{bg}{WHITE_FG} * CLAUDE 3.5 SONNET {RESET} + +{bg}{WHITE_FG}❯{RESET} runs code +{bg}{WHITE_FG}≣{RESET} edits files +{bg}{WHITE_FG}⌘{RESET} requires approval +""", + "white_on_color_2": lambda bg, fg: f""" +{bg}{WHITE_FG} * CLAUDE 3.5 SONNET {RESET} + +{bg}{WHITE_FG}❯{RESET} interpreter {bg}{WHITE_FG}≣{RESET} file editor +{bg}{WHITE_FG}⌘{RESET} actions require approval +""", + "black_on_color": lambda bg, fg: f""" +{bg}{BLACK_FG} * CLAUDE 3.5 SONNET {RESET} + +{bg}{BLACK_FG}❯{RESET} runs code +{bg}{BLACK_FG}≣{RESET} edits files +{bg}{BLACK_FG}⌘{RESET} requires approval +""", + "minimal": lambda bg, fg: f""" +* CLAUDE 3.5 SONNET + +{fg}$ runs code +≣ edits files +! requires approval{RESET} +""", + "double_line": lambda bg, fg: f""" +{bg} * CLAUDE 3.5 SONNET {RESET} + +{fg}❯ runs code{RESET} {fg}≣ edits files{RESET} +{fg}⌘ requires approval{RESET} +""", + "modern": lambda bg, fg: f""" +{bg} >> CLAUDE 3.5 SONNET << {RESET} + +{fg}▶{RESET} executes commands +{fg}□{RESET} manages files +{fg}△{RESET} needs approval +""", + "technical": lambda bg, fg: f""" +{bg} CLAUDE 3.5 SONNET {RESET} + +{fg}${RESET} runs code +{fg}#{RESET} edits files +{fg}@{RESET} needs ok +""", + "technical_2": lambda bg, fg: f""" +{bg}{WHITE_FG} CLAUDE 3.5 SONNET {RESET} + +# edits files +$ executes commands +@ actions require approval +""", + "technical_3": lambda bg, fg: f""" +{bg}{WHITE_FG} CLAUDE 3.5 SONNET {RESET} + +{fg}# file editor +$ bash executor +@ requires approval{RESET} +""", + "brackets": lambda bg, fg: f""" +{bg} [ CLAUDE 3.5 SONNET ] {RESET} + +{fg}[>]{RESET} run commands +{fg}[=]{RESET} file operations +{fg}[!]{RESET} elevated access +""", + "ascii_art": lambda bg, fg: f""" +{bg} | CLAUDE SONNET | {RESET} + +{fg}>>>{RESET} execute code +{fg}[=]{RESET} modify files +{fg}(!){RESET} request approval +""", + } + LAYOUTS = { + k: v + for k, v in LAYOUTS.items() + if k + in ["basic_background", "minimal", "technical", "technical_2", "technical_3"] + } + + # Print each layout with different color combinations import random + layout_items = list(LAYOUTS.items()) + color_items = list(COLORS.items()) + random.shuffle(layout_items) + random.shuffle(color_items) + + for color_name, (BG, FG) in color_items: + for layout_name, layout in layout_items: + print(f"Style: {layout_name} with {color_name}\n\n\n") + print("$: interpreter") + print(layout(BG, FG)) + print("> make a react project") + print("\n" * 10) + + +def welcome_message(args): + WHITE_FG = "\033[97m" + BLUE_BG = "\033[48;5;27m" # Dark blue background (not navy) + BLUE_FG = "\033[38;5;27m" # Matching dark blue foreground + BLUE_BG = "\033[48;5;21m" # Dark blue background (not navy) + BLUE_FG = "\033[38;5;21m" # Matching dark blue foreground + RESET = "\033[0m" + tips = [ - # "You can type `i` in your terminal to use Open Interpreter.", - "**Tip:** Type `wtf` in your terminal to instantly fix the last error.", - # "**Tip:** Type `wtf` in your terminal to have Open Interpreter fix the last error.", - '**Tip:** You can paste content into Open Interpreter by typing `"""` first.', - # "**Tip:** Type prompts after `i` in your terminal, for example, `i want deno`.", - "**Tip:** You can type `i [your prompt]` directly into your terminal, e.g. `i want a venv`.", # \n\nThese are all valid commands: `i want deno`, `i dont understand`, `i want a venv`", - # "**Tip:** Type your prompt directly into your CLI by starting with `i `, like `i want node`.", # \n\nThese are all valid commands: `i want deno`, `i dont understand`, `i want a venv`", - # "Our desktop app provides the best experience. Type `d` for early access.", - # "**Tip:** Reduce display resolution for better performance.", + 'Use """ for multi-line input', + "Try the wtf command to fix the last error", + "Press Ctrl+C to cancel", + "Messages starting with $ run in the shell", ] - random_tip = random.choice(tips) - - model = args["model"] - - if model == "claude-3-5-sonnet-20241022": - model = "CLAUDE-3.5-SONNET" - - model = f"` ✳ {model.upper()} `" # {"-" * (terminal_width - len(model))} # ⎇ - - if args["tool_calling"] == False: - args["tools"] = ["interpreter"] - - tool_displays = [] - for tool in ["interpreter", "editor", "gui"]: - if args["tools"] and tool in args["tools"]: - if tool == "interpreter": - tool_displays.append("` ❯ INTERPRETER `") - elif tool == "editor": - tool_displays.append("` ❚ FILE EDITOR `") - elif tool == "gui": - tool_displays.append("` ✳ GUI CONTROL `") - else: - if tool == "interpreter": - tool_displays.append(" " * len(" ❯ INTERPRETER ")) - elif tool == "editor": - tool_displays.append(" " * len(" ❚ FILE EDITOR ")) - elif tool == "gui": - tool_displays.append(" " * len(" ✳ GUI CONTROL ")) - - # Sort tool_displays so that empty tools are at the end - tool_displays = sorted( - tool_displays, key=lambda x: x == " " * len(" ❯ INTERPRETER ") - ) + print( + f""" +{BLUE_BG}{WHITE_FG} CLAUDE 3.5 SONNET {RESET} - auto_run_display = ( - "` ! AUTOMATIC (UNSAFE) `" if args["auto_run"] else "` ? REQUIRES PERMISSION `" +# edits files +$ executes commands +@ actions require approval +""" ) - second_column_from_left = 20 - markdown_text = f"""● - -Welcome to **Open Interpreter**. +# Tip: {random.choice(tips)} + +# def welcome_message(args): +# print(f""" +# \033[7m GPT-5 \033[0m + +# # edits files +# $ executes commands +# @ actions require approval +# """) + + +def welcome_message(args): + print( + f""" +Open Interpreter 1.0.0 +Copyright (C) 2024 Open Interpreter Team +Licensed under GNU AGPL v3.0 +Maintained by automated systems + +A natural language interface for your computer. + +Usage: i [prompt] + or: interpreter [options] + +Documentation: docs.openinterpreter.com +Run 'interpreter --help' for full options +""" + ) -**TOOLS**{" "*(second_column_from_left-len("TOOLS"))}**MODEL** -{tool_displays[0]}{" "*(second_column_from_left-len(" ❯ INTERPRETER "))}{model} -{tool_displays[1]}{" "*(second_column_from_left-len(" ❯ INTERPRETER "))}**TOOL EXECUTION** -{tool_displays[2]}{" "*(second_column_from_left-len(" ❯ INTERPRETER "))}{auto_run_display} +def welcome_message(): + print( + f""" +Open Interpreter 1.0.0 +Licensed under GNU AGPL v3.0 -{random_tip} +A natural language interface for your computer. +Usage: interpreter [prompt] [-m model] [-t temp] [-k key] [options] +Execute natural language commands on your computer -{"─" * terminal_width} + -m, --model Specify the language model to use + -t, --temp Set temperature (0-1) for model responses + -k, --key Set API key for the model provider + -p, --profile Load settings from profile file + --auto-run Run commands without confirmation + --no-tools Disable tool/function calling + --debug Enable debug logging + --serve Start in server mode + +example: interpreter "create a python script" +example: interpreter -m gpt-4 "analyze data.csv" +example: interpreter --auto-run "install nodejs" +example: interpreter --profile work.json """ + ) + - """ - **Warning:** This AI has full system access and can modify files, install software, and execute commands. By continuing, you accept all risks and responsibility. +def welcome_message(): + print( + f""" +Open Interpreter 1.0.0 +Licensed under GNU AGPL v3.0 - Move your mouse to any corner of the screen to exit. - """ +A modern command-line assistant. - # for chunk in stream_text(markdown_text, max_chunk=1, min_delay=0.0001, max_delay=0.001): - # renderer.feed(chunk) +Usage: interpreter [prompt] [-m model] [-t temp] [-k key] [options] +Execute natural language commands on your computer - renderer.feed(markdown_text) + -m, --model Specify the language model to use + -t, --temp Set temperature (0-1) for model responses + -k, --key Set API key for the model provider + -p, --profile Load settings from profile file + --auto-run Run commands without confirmation + --no-tools Disable tool/function calling + --debug Enable debug logging + --serve Start in server mode - renderer.close() +example: interpreter "create a python script" +example: interpreter -m gpt-4 "analyze data.csv" +example: interpreter --auto-run "install nodejs" +example: interpreter --profile work.json +""" + ) + + +def welcome_message(): + print( + f""" +Open Interpreter 1.0.0 +Copyright (C) 2024 Open Interpreter Team +Licensed under GNU AGPL v3.0 +A modern command-line assistant. -# ⧳ ❚ ❯ ✦ ⬤ ● ▶ ⚈ ⌖ ⎋ ⬤ ◉ ⎇ +Usage: i [prompt] + or: interpreter [options] + +Documentation: docs.openinterpreter.com +Run 'interpreter --help' for full options +""" + ) diff --git a/interpreter_1/profiles.py b/interpreter_1/profiles.py index c956f75e9..26a6d5c47 100644 --- a/interpreter_1/profiles.py +++ b/interpreter_1/profiles.py @@ -32,7 +32,7 @@ class Profile: def __init__(self): # Default values if no profile exists # Model configuration - self.model = "claude-3-5-sonnet-20241022" # The LLM model to use + self.model = "anthropic/claude-3-5-sonnet-latest" # The LLM model to use self.provider = ( None # The model provider (e.g. anthropic, openai) None will auto-detect ) diff --git a/interpreter_1/server.py b/interpreter_1/server.py index dd47bf221..2b8b5f218 100644 --- a/interpreter_1/server.py +++ b/interpreter_1/server.py @@ -33,7 +33,7 @@ def __init__(self, interpreter): self.app = FastAPI(title="Open Interpreter API") # Setup routes - self.app.post("/v1/chat/completions")(self.chat_completion) + self.app.post("/chat/completions")(self.chat_completion) async def chat_completion(self, request: Request): """Main chat completion endpoint"""