diff --git a/README.md b/README.md index 0271ae8..b5e82da 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Minimal Claude Code alternative. Single Python file, zero dependencies, ~250 lin Built using Claude Code, then used to build itself. -![screenshot](https://d1pz4mbco29rws.cloudfront.net/public/nanocode.png) +![screenshot](screenshot.png) ## Features diff --git a/config.json b/config.json index b22fe68..a7b8593 100644 --- a/config.json +++ b/config.json @@ -3,5 +3,5 @@ "max_iters": 15, "lm": "openai/gpt-5.2-codex", "api_base": "https://openrouter.ai/api/v1", - "max_tokens": 8192 + "max_tokens": 16000 } \ No newline at end of file diff --git a/nanocode.py b/nanocode.py index 6445985..d9ad544 100644 --- a/nanocode.py +++ b/nanocode.py @@ -25,6 +25,7 @@ MAGENTA = "\033[35m" # --- Display utilities --- + def separator(): """Return a horizontal separator line that fits the terminal width.""" return f"{DIM}{'─' * min(os.get_terminal_size().columns, 80)}{RESET}" @@ -37,6 +38,7 @@ def render_markdown(text): # --- File operations --- + def read_file(path: str, offset: int = 0, limit: int = None) -> str: """Read file contents with line numbers. @@ -138,6 +140,7 @@ def grep_files(pattern: str, path: str = ".") -> str: # --- Shell operations --- + def run_bash(cmd: str) -> str: """Run a shell command and return output. @@ -148,9 +151,7 @@ def run_bash(cmd: str) -> str: Command output (stdout and stderr combined) """ proc = subprocess.Popen( - cmd, shell=True, - stdout=subprocess.PIPE, stderr=subprocess.STDOUT, - text=True + cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True ) output_lines = [] try: @@ -196,14 +197,20 @@ def select_model(): while True: try: - choice = input(f"\n{BOLD}{BLUE}❯{RESET} Enter choice (1-8 or c): ").strip().lower() + choice = ( + input(f"\n{BOLD}{BLUE}❯{RESET} Enter choice (1-8 or c): ") + .strip() + .lower() + ) if choice in AVAILABLE_MODELS: name, model_id = AVAILABLE_MODELS[choice] print(f"{GREEN}⏺ Selected: {name}{RESET}") return model_id elif choice == "c": - custom_model = input(f"{BOLD}{BLUE}❯{RESET} Enter model ID (e.g., openai/gpt-4): ").strip() + custom_model = input( + f"{BOLD}{BLUE}❯{RESET} Enter model ID (e.g., openai/gpt-4): " + ).strip() if custom_model: print(f"{GREEN}⏺ Selected custom model: {custom_model}{RESET}") return custom_model @@ -218,12 +225,18 @@ def select_model(): # --- DSPy Signature --- + class CodingAssistant(dspy.Signature): """You are a concise coding assistant. Help the user with their coding task by using the available tools to read, write, edit files, search the codebase, and run commands.""" task: str = dspy.InputField(desc="The user's coding task or question") - answer: str = dspy.OutputField(desc="Your response to the user after completing the task") - affected_files: list[str] = dspy.OutputField(desc="List of files that were written or modified during the task") + answer: str = dspy.OutputField( + desc="Your response to the user after completing the task" + ) + affected_files: list[str] = dspy.OutputField( + desc="List of files that were written or modified during the task" + ) + # ReAct agent with tools @@ -235,7 +248,7 @@ class ToolLoggingCallback(BaseCallback): def on_tool_start(self, call_id, instance, inputs): """Log when a tool starts executing.""" - tool_name = instance.name if hasattr(instance, 'name') else str(instance) + tool_name = instance.name if hasattr(instance, "name") else str(instance) # Format args nicely args_str = ", ".join(f"{k}={repr(v)[:50]}" for k, v in inputs.items()) print(f" {MAGENTA}⏺ {tool_name}({args_str}){RESET}", flush=True) @@ -248,10 +261,12 @@ class ToolLoggingCallback(BaseCallback): def on_module_end(self, call_id, outputs, exception): """Log when the finish tool is called (ReAct completion).""" # Check if this is a ReAct prediction with tool_calls - if outputs and 'tool_calls' in outputs: - for call in outputs['tool_calls']: - args_str = ", ".join(f"{k}={repr(v)[:50]}" for k, v in call.args.items()) - if call.name == 'finish': + if outputs and "tool_calls" in outputs: + for call in outputs["tool_calls"]: + args_str = ", ".join( + f"{k}={repr(v)[:50]}" for k, v in call.args.items() + ) + if call.name == "finish": print(f" {GREEN}⏺ finish{RESET}", flush=True) else: print(f" {MAGENTA}⏺ {call.name}({args_str}){RESET}", flush=True) @@ -261,7 +276,8 @@ class AgentConfig(PrecompiledConfig): max_iters: int = 15 lm: str = "openrouter/anthropic/claude-3.5-sonnet" # Default fallback api_base: str = "https://openrouter.ai/api/v1" - max_tokens: int = 8192 + max_tokens: int = 16000 + class AgentProgram(PrecompiledProgram): config: AgentConfig @@ -273,8 +289,14 @@ class AgentProgram(PrecompiledProgram): # Configure logging callback globally dspy.settings.configure(callbacks=[ToolLoggingCallback()]) - agent = dspy.ReAct(CodingAssistant, tools=tools, max_iters=self.config.max_iters) - lm = dspy.LM(self.config.lm, api_base=self.config.api_base, max_tokens=self.config.max_tokens) + agent = dspy.ReAct( + CodingAssistant, tools=tools, max_iters=self.config.max_iters + ) + lm = dspy.LM( + self.config.lm, + api_base=self.config.api_base, + max_tokens=self.config.max_tokens, + ) agent.set_lm(lm) self.agent = agent @@ -282,6 +304,7 @@ class AgentProgram(PrecompiledProgram): assert task, "Task cannot be empty" return self.agent(task=task) + # --- Main --- @@ -299,7 +322,9 @@ def main(): config.lm = model agent = AgentProgram(config) - print(f"{BOLD}nanocode-dspy{RESET} | {DIM}{agent.config.lm} | {os.getcwd()}{RESET}\n") + print( + f"{BOLD}nanocode-dspy{RESET} | {DIM}{agent.config.lm} | {os.getcwd()}{RESET}\n" + ) # Conversation history for context history = [] @@ -345,6 +370,7 @@ def main(): break except Exception as err: import traceback + traceback.print_exc() print(f"{RED}⏺ Error: {err}{RESET}") @@ -352,5 +378,4 @@ def main(): if __name__ == "__main__": agent = AgentProgram(AgentConfig(lm="openai/gpt-5.2-codex")) agent.push_to_hub("farouk1/nanocode") - #main() - + # main() diff --git a/program.json b/program.json index dabddb8..9ef50e4 100644 --- a/program.json +++ b/program.json @@ -37,7 +37,7 @@ "launch_kwargs": {}, "train_kwargs": {}, "temperature": null, - "max_tokens": 8192, + "max_tokens": 16000, "api_base": "https://openrouter.ai/api/v1" } }, @@ -79,7 +79,7 @@ "launch_kwargs": {}, "train_kwargs": {}, "temperature": null, - "max_tokens": 8192, + "max_tokens": 16000, "api_base": "https://openrouter.ai/api/v1" } },