(no commit message)
This commit is contained in:
@@ -4,7 +4,7 @@ Minimal Claude Code alternative. Single Python file, zero dependencies, ~250 lin
|
|||||||
|
|
||||||
Built using Claude Code, then used to build itself.
|
Built using Claude Code, then used to build itself.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
|||||||
@@ -3,5 +3,5 @@
|
|||||||
"max_iters": 15,
|
"max_iters": 15,
|
||||||
"lm": "openai/gpt-5.2-codex",
|
"lm": "openai/gpt-5.2-codex",
|
||||||
"api_base": "https://openrouter.ai/api/v1",
|
"api_base": "https://openrouter.ai/api/v1",
|
||||||
"max_tokens": 8192
|
"max_tokens": 16000
|
||||||
}
|
}
|
||||||
59
nanocode.py
59
nanocode.py
@@ -25,6 +25,7 @@ MAGENTA = "\033[35m"
|
|||||||
|
|
||||||
# --- Display utilities ---
|
# --- Display utilities ---
|
||||||
|
|
||||||
|
|
||||||
def separator():
|
def separator():
|
||||||
"""Return a horizontal separator line that fits the terminal width."""
|
"""Return a horizontal separator line that fits the terminal width."""
|
||||||
return f"{DIM}{'─' * min(os.get_terminal_size().columns, 80)}{RESET}"
|
return f"{DIM}{'─' * min(os.get_terminal_size().columns, 80)}{RESET}"
|
||||||
@@ -37,6 +38,7 @@ def render_markdown(text):
|
|||||||
|
|
||||||
# --- File operations ---
|
# --- File operations ---
|
||||||
|
|
||||||
|
|
||||||
def read_file(path: str, offset: int = 0, limit: int = None) -> str:
|
def read_file(path: str, offset: int = 0, limit: int = None) -> str:
|
||||||
"""Read file contents with line numbers.
|
"""Read file contents with line numbers.
|
||||||
|
|
||||||
@@ -138,6 +140,7 @@ def grep_files(pattern: str, path: str = ".") -> str:
|
|||||||
|
|
||||||
# --- Shell operations ---
|
# --- Shell operations ---
|
||||||
|
|
||||||
|
|
||||||
def run_bash(cmd: str) -> str:
|
def run_bash(cmd: str) -> str:
|
||||||
"""Run a shell command and return output.
|
"""Run a shell command and return output.
|
||||||
|
|
||||||
@@ -148,9 +151,7 @@ def run_bash(cmd: str) -> str:
|
|||||||
Command output (stdout and stderr combined)
|
Command output (stdout and stderr combined)
|
||||||
"""
|
"""
|
||||||
proc = subprocess.Popen(
|
proc = subprocess.Popen(
|
||||||
cmd, shell=True,
|
cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True
|
||||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
|
||||||
text=True
|
|
||||||
)
|
)
|
||||||
output_lines = []
|
output_lines = []
|
||||||
try:
|
try:
|
||||||
@@ -196,14 +197,20 @@ def select_model():
|
|||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
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:
|
if choice in AVAILABLE_MODELS:
|
||||||
name, model_id = AVAILABLE_MODELS[choice]
|
name, model_id = AVAILABLE_MODELS[choice]
|
||||||
print(f"{GREEN}⏺ Selected: {name}{RESET}")
|
print(f"{GREEN}⏺ Selected: {name}{RESET}")
|
||||||
return model_id
|
return model_id
|
||||||
elif choice == "c":
|
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:
|
if custom_model:
|
||||||
print(f"{GREEN}⏺ Selected custom model: {custom_model}{RESET}")
|
print(f"{GREEN}⏺ Selected custom model: {custom_model}{RESET}")
|
||||||
return custom_model
|
return custom_model
|
||||||
@@ -218,12 +225,18 @@ def select_model():
|
|||||||
|
|
||||||
# --- DSPy Signature ---
|
# --- DSPy Signature ---
|
||||||
|
|
||||||
|
|
||||||
class CodingAssistant(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."""
|
"""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")
|
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")
|
answer: str = dspy.OutputField(
|
||||||
affected_files: list[str] = dspy.OutputField(desc="List of files that were written or modified during the task")
|
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
|
# ReAct agent with tools
|
||||||
|
|
||||||
@@ -235,7 +248,7 @@ class ToolLoggingCallback(BaseCallback):
|
|||||||
|
|
||||||
def on_tool_start(self, call_id, instance, inputs):
|
def on_tool_start(self, call_id, instance, inputs):
|
||||||
"""Log when a tool starts executing."""
|
"""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
|
# Format args nicely
|
||||||
args_str = ", ".join(f"{k}={repr(v)[:50]}" for k, v in inputs.items())
|
args_str = ", ".join(f"{k}={repr(v)[:50]}" for k, v in inputs.items())
|
||||||
print(f" {MAGENTA}⏺ {tool_name}({args_str}){RESET}", flush=True)
|
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):
|
def on_module_end(self, call_id, outputs, exception):
|
||||||
"""Log when the finish tool is called (ReAct completion)."""
|
"""Log when the finish tool is called (ReAct completion)."""
|
||||||
# Check if this is a ReAct prediction with tool_calls
|
# Check if this is a ReAct prediction with tool_calls
|
||||||
if outputs and 'tool_calls' in outputs:
|
if outputs and "tool_calls" in outputs:
|
||||||
for call in outputs['tool_calls']:
|
for call in outputs["tool_calls"]:
|
||||||
args_str = ", ".join(f"{k}={repr(v)[:50]}" for k, v in call.args.items())
|
args_str = ", ".join(
|
||||||
if call.name == 'finish':
|
f"{k}={repr(v)[:50]}" for k, v in call.args.items()
|
||||||
|
)
|
||||||
|
if call.name == "finish":
|
||||||
print(f" {GREEN}⏺ finish{RESET}", flush=True)
|
print(f" {GREEN}⏺ finish{RESET}", flush=True)
|
||||||
else:
|
else:
|
||||||
print(f" {MAGENTA}⏺ {call.name}({args_str}){RESET}", flush=True)
|
print(f" {MAGENTA}⏺ {call.name}({args_str}){RESET}", flush=True)
|
||||||
@@ -261,7 +276,8 @@ class AgentConfig(PrecompiledConfig):
|
|||||||
max_iters: int = 15
|
max_iters: int = 15
|
||||||
lm: str = "openrouter/anthropic/claude-3.5-sonnet" # Default fallback
|
lm: str = "openrouter/anthropic/claude-3.5-sonnet" # Default fallback
|
||||||
api_base: str = "https://openrouter.ai/api/v1"
|
api_base: str = "https://openrouter.ai/api/v1"
|
||||||
max_tokens: int = 8192
|
max_tokens: int = 16000
|
||||||
|
|
||||||
|
|
||||||
class AgentProgram(PrecompiledProgram):
|
class AgentProgram(PrecompiledProgram):
|
||||||
config: AgentConfig
|
config: AgentConfig
|
||||||
@@ -273,8 +289,14 @@ class AgentProgram(PrecompiledProgram):
|
|||||||
# Configure logging callback globally
|
# Configure logging callback globally
|
||||||
dspy.settings.configure(callbacks=[ToolLoggingCallback()])
|
dspy.settings.configure(callbacks=[ToolLoggingCallback()])
|
||||||
|
|
||||||
agent = dspy.ReAct(CodingAssistant, tools=tools, max_iters=self.config.max_iters)
|
agent = dspy.ReAct(
|
||||||
lm = dspy.LM(self.config.lm, api_base=self.config.api_base, max_tokens=self.config.max_tokens)
|
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)
|
agent.set_lm(lm)
|
||||||
self.agent = agent
|
self.agent = agent
|
||||||
|
|
||||||
@@ -282,6 +304,7 @@ class AgentProgram(PrecompiledProgram):
|
|||||||
assert task, "Task cannot be empty"
|
assert task, "Task cannot be empty"
|
||||||
return self.agent(task=task)
|
return self.agent(task=task)
|
||||||
|
|
||||||
|
|
||||||
# --- Main ---
|
# --- Main ---
|
||||||
|
|
||||||
|
|
||||||
@@ -299,7 +322,9 @@ def main():
|
|||||||
config.lm = model
|
config.lm = model
|
||||||
|
|
||||||
agent = AgentProgram(config)
|
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
|
# Conversation history for context
|
||||||
history = []
|
history = []
|
||||||
@@ -345,6 +370,7 @@ def main():
|
|||||||
break
|
break
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
print(f"{RED}⏺ Error: {err}{RESET}")
|
print(f"{RED}⏺ Error: {err}{RESET}")
|
||||||
|
|
||||||
@@ -353,4 +379,3 @@ if __name__ == "__main__":
|
|||||||
agent = AgentProgram(AgentConfig(lm="openai/gpt-5.2-codex"))
|
agent = AgentProgram(AgentConfig(lm="openai/gpt-5.2-codex"))
|
||||||
agent.push_to_hub("farouk1/nanocode")
|
agent.push_to_hub("farouk1/nanocode")
|
||||||
# main()
|
# main()
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
"launch_kwargs": {},
|
"launch_kwargs": {},
|
||||||
"train_kwargs": {},
|
"train_kwargs": {},
|
||||||
"temperature": null,
|
"temperature": null,
|
||||||
"max_tokens": 8192,
|
"max_tokens": 16000,
|
||||||
"api_base": "https://openrouter.ai/api/v1"
|
"api_base": "https://openrouter.ai/api/v1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -79,7 +79,7 @@
|
|||||||
"launch_kwargs": {},
|
"launch_kwargs": {},
|
||||||
"train_kwargs": {},
|
"train_kwargs": {},
|
||||||
"temperature": null,
|
"temperature": null,
|
||||||
"max_tokens": 8192,
|
"max_tokens": 16000,
|
||||||
"api_base": "https://openrouter.ai/api/v1"
|
"api_base": "https://openrouter.ai/api/v1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user