mini-code-cli 0.0.6__py3-none-any.whl → 0.0.7.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.
- mini_code_cli/__init__.py +2 -0
- mini_code_cli/agent_loop.py +157 -60
- mini_code_cli/prompts.py +15 -7
- mini_code_cli/tools.py +1 -1
- mini_code_cli/utils.py +2 -2
- {mini_code_cli-0.0.6.dist-info → mini_code_cli-0.0.7.1.dist-info}/METADATA +9 -2
- mini_code_cli-0.0.7.1.dist-info/RECORD +11 -0
- mini_code_cli-0.0.6.dist-info/RECORD +0 -11
- {mini_code_cli-0.0.6.dist-info → mini_code_cli-0.0.7.1.dist-info}/WHEEL +0 -0
- {mini_code_cli-0.0.6.dist-info → mini_code_cli-0.0.7.1.dist-info}/entry_points.txt +0 -0
- {mini_code_cli-0.0.6.dist-info → mini_code_cli-0.0.7.1.dist-info}/licenses/LICENSE +0 -0
- {mini_code_cli-0.0.6.dist-info → mini_code_cli-0.0.7.1.dist-info}/top_level.txt +0 -0
mini_code_cli/__init__.py
CHANGED
mini_code_cli/agent_loop.py
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Usage:
|
|
3
|
+
mini-code
|
|
4
|
+
|
|
5
|
+
Prompt:
|
|
6
|
+
|
|
7
|
+
write a similar model.py file but for a LSTM based language model. Make it minimal and call it model_lstm.py it should still be compatible with eval.py
|
|
8
|
+
|
|
9
|
+
"""
|
|
10
|
+
|
|
1
11
|
import os
|
|
2
12
|
import json
|
|
3
13
|
|
|
@@ -23,17 +33,59 @@ def save_history(messages):
|
|
|
23
33
|
with open("history.json", "w") as f:
|
|
24
34
|
json.dump(messages, f, indent=2)
|
|
25
35
|
|
|
36
|
+
|
|
26
37
|
def parse_args():
|
|
27
38
|
parser = argparse.ArgumentParser(description="Agent loop for interacting with a language model")
|
|
28
39
|
parser.add_argument("--url", type=str, default="http://0.0.0.0:30000", help="API host")
|
|
40
|
+
parser.add_argument("--system_prompt", type=str, help="API host")
|
|
29
41
|
parser.add_argument("--api-key", type=str, default=None, help="API key for authentication")
|
|
30
42
|
parser.add_argument("--allowed-dir", type=str, default=".", help="Allowed directory for file operations")
|
|
31
|
-
parser.add_argument("--max-tokens", type=int,
|
|
43
|
+
parser.add_argument("--max-tokens", type=int, help="Maximum tokens for LLM response")
|
|
32
44
|
parser.add_argument("--temperature", type=float, default=0.0, help="Temperature for LLM")
|
|
33
45
|
parser.add_argument("--model", type=str, default="default", help="Model name")
|
|
46
|
+
parser.add_argument("--step_by_step", action="store_true", help="Whether to run the agent step by step. Default is to run in 'Permission' mode.")
|
|
47
|
+
parser.add_argument("--ask_permission", action="store_true", help="Ask for permission before any tool call.")
|
|
48
|
+
|
|
34
49
|
args = parser.parse_args()
|
|
35
50
|
return args
|
|
36
51
|
|
|
52
|
+
def print_welcome(allowed_dir, server_url, args):
|
|
53
|
+
# Calculate the length of the longest line inside the box
|
|
54
|
+
version = __import__('mini_code_cli').__version__
|
|
55
|
+
|
|
56
|
+
permission_mode = "All tool calls require permission." if args.ask_permission else "Tool calls will execute without asking for permission."
|
|
57
|
+
agent_mode = "Agent is in manual-mode." if args.step_by_step else "Agent is in auto-mode."
|
|
58
|
+
|
|
59
|
+
lines = [
|
|
60
|
+
f" MiniCodeCLI (v{version})",
|
|
61
|
+
" Type 'quit' or 'exit' to exit the loop.",
|
|
62
|
+
f" Allowed dir: {allowed_dir}",
|
|
63
|
+
f" Server URL: {server_url}",
|
|
64
|
+
f" {permission_mode}",
|
|
65
|
+
f" {agent_mode}",
|
|
66
|
+
" Shell disabled."
|
|
67
|
+
]
|
|
68
|
+
max_len = max(len(line) for line in lines)
|
|
69
|
+
margin = 2 # Space on each side inside the box
|
|
70
|
+
inner_width = max_len + 2 * margin
|
|
71
|
+
# Build the box
|
|
72
|
+
top_bottom = "╔" + "═" * inner_width + "╗"
|
|
73
|
+
print(top_bottom)
|
|
74
|
+
for line in lines:
|
|
75
|
+
# Pad the line to inner_width characters
|
|
76
|
+
padded = line.ljust(inner_width)
|
|
77
|
+
print(f"║{padded}║")
|
|
78
|
+
bottom = "╚" + "═" * inner_width + "╝"
|
|
79
|
+
print(bottom)
|
|
80
|
+
|
|
81
|
+
def print_help(allowed_dir, server_url, args):
|
|
82
|
+
print_welcome(allowed_dir, server_url, args)
|
|
83
|
+
|
|
84
|
+
help_message = ""
|
|
85
|
+
help_message += "Manual-mode means the agent will ask for user input after each LLM prediction." if args.step_by_step else "Auto-mode means the agent will keep calling the LLM until no further LLM calls are possible."
|
|
86
|
+
|
|
87
|
+
print(f"{BOLD}{GREEN}HELP:{RESET} {help_message}")
|
|
88
|
+
|
|
37
89
|
def main():
|
|
38
90
|
args = parse_args()
|
|
39
91
|
|
|
@@ -43,34 +95,50 @@ def main():
|
|
|
43
95
|
tools_dict = tools.util_get_tools_dict()
|
|
44
96
|
llm_tools_dict = tools.util_get_tools()
|
|
45
97
|
|
|
46
|
-
|
|
98
|
+
if args.system_prompt:
|
|
99
|
+
system_prompt = args.system_prompt
|
|
100
|
+
else:
|
|
101
|
+
system_prompt = prompts.system_prompt(tools_dict=tools_dict, allowed_dir=allowed_dir)
|
|
47
102
|
|
|
48
103
|
messages = [{"role": "system", "content": system_prompt}]
|
|
49
104
|
|
|
50
|
-
|
|
51
|
-
╔═══════════════════════════════════════════════════════════════════╗
|
|
52
|
-
║ Coding Agent Loop Started\t\t\t\t\t║
|
|
53
|
-
║ Type 'quit' or 'exit' to exit the loop.\t\t\t\t║
|
|
54
|
-
║ Allowed dir: {allowed_dir}\t║
|
|
55
|
-
╚═══════════════════════════════════════════════════════════════════╝
|
|
56
|
-
""")
|
|
105
|
+
print_welcome(allowed_dir, server_url, args)
|
|
57
106
|
|
|
107
|
+
llm_response_flag = False
|
|
108
|
+
llm_tool_response_flag = False
|
|
109
|
+
llm_repeat_flag = False
|
|
110
|
+
tool_count = 0
|
|
111
|
+
|
|
58
112
|
while True:
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if
|
|
113
|
+
if llm_tool_response_flag and not args.step_by_step: #skip user input and try to call the model again until there no more tool calls.
|
|
114
|
+
print(f"{BOLD}{YELLOW}System:{RESET} Skipping user input. Continuing with LLM calls until no more tool calls.")
|
|
115
|
+
llm_repeat_flag = True
|
|
116
|
+
else:
|
|
117
|
+
# get user input
|
|
118
|
+
if llm_repeat_flag:
|
|
119
|
+
print(f"{BOLD}{YELLOW}System:{RESET} Called {tool_count} tools in total. Exiting tool loop.")
|
|
120
|
+
|
|
121
|
+
tool_count = 0
|
|
122
|
+
try:
|
|
123
|
+
print("-"*30)
|
|
124
|
+
user_input = input(f"{BOLD}{CYAN}You:{RESET} ")
|
|
125
|
+
if user_input.lower() in ["quit", "exit", "/quit", "/exit"]:
|
|
126
|
+
print("\nThank you. Goodbye! Saving history...")
|
|
127
|
+
save_history(messages)
|
|
128
|
+
break
|
|
129
|
+
except KeyboardInterrupt:
|
|
65
130
|
print("\nThank you. Goodbye! Saving history...")
|
|
66
131
|
save_history(messages)
|
|
67
132
|
break
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
133
|
+
|
|
134
|
+
if user_input == "/help":
|
|
135
|
+
print_help(allowed_dir, server_url, args)
|
|
136
|
+
continue
|
|
72
137
|
|
|
73
|
-
|
|
138
|
+
messages.append({"role": "user", "content": user_input})
|
|
139
|
+
|
|
140
|
+
llm_response_flag = False
|
|
141
|
+
llm_tool_response_flag = False
|
|
74
142
|
|
|
75
143
|
response_text, tool_calls = utils.call_openai_server(
|
|
76
144
|
messages,
|
|
@@ -82,54 +150,83 @@ def main():
|
|
|
82
150
|
)
|
|
83
151
|
|
|
84
152
|
if response_text:
|
|
85
|
-
print(f"{BOLD}{GREEN}Assistant:{RESET} {response_text}")
|
|
86
|
-
messages.append({"role": "assistant", "content": response_text})
|
|
87
|
-
else:
|
|
88
153
|
llm_response_flag = True
|
|
154
|
+
print(f"{BOLD}{GREEN}Assistant:{RESET} {response_text}")
|
|
89
155
|
|
|
90
156
|
if tool_calls:
|
|
157
|
+
llm_tool_response_flag = True
|
|
158
|
+
|
|
159
|
+
messages.append({
|
|
160
|
+
"role": "assistant",
|
|
161
|
+
"content": response_text,
|
|
162
|
+
"tool_calls": tool_calls
|
|
163
|
+
})
|
|
91
164
|
# Tool call parsing and execution
|
|
92
165
|
print(f"{BOLD}{YELLOW}Tool Call:{RESET} LLM is trying to call {len(tool_calls)} tools.")
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
166
|
+
|
|
167
|
+
for call in tool_calls:
|
|
168
|
+
tool_count += 1
|
|
169
|
+
|
|
170
|
+
call_id = call.get("id", "")
|
|
171
|
+
name = call.get("function", {}).get("name", "")
|
|
172
|
+
params_str = call.get("function", {}).get("arguments", "{}")
|
|
173
|
+
try:
|
|
174
|
+
tool_params = json.loads(params_str)
|
|
175
|
+
except json.JSONDecodeError:
|
|
176
|
+
print(f"{BOLD}{RED}Warning:{RESET} Error parsing arguments for tool {name}")
|
|
177
|
+
continue
|
|
178
|
+
|
|
179
|
+
if name in tools_dict:
|
|
180
|
+
# Check allowed directory for file operations
|
|
181
|
+
if name in ["read_file", "write_file", "grep", "glob"]:
|
|
182
|
+
filepath = tool_params.get("filepath", "")
|
|
183
|
+
if name == "write_file":
|
|
107
184
|
filepath = tool_params.get("filepath", "")
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
"
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
185
|
+
if filepath and not os.path.abspath(filepath).startswith(allowed_dir):
|
|
186
|
+
print(f"{BOLD}{RED}Warning:{RESET} File operation not allowed outside allowed directory: {allowed_dir}")
|
|
187
|
+
continue
|
|
188
|
+
|
|
189
|
+
func = tools_dict[name]["function"]
|
|
190
|
+
# asking permissions
|
|
191
|
+
if args.ask_permission:
|
|
192
|
+
print(f"{BOLD}{YELLOW}Tool permission:{RESET} Are you sure you want to call the tool?")
|
|
193
|
+
print(f"name: {name}, parameters: {params_str}")
|
|
194
|
+
try:
|
|
195
|
+
permission_input = input("Type: 'y' or 'yes'\n")
|
|
196
|
+
except KeyboardInterrupt:
|
|
197
|
+
print("\nThank you. Goodbye! Saving history...")
|
|
198
|
+
save_history(messages)
|
|
199
|
+
sys.exit(0)
|
|
200
|
+
|
|
201
|
+
if not permission_input in ['y', 'yes']:
|
|
202
|
+
print(f"{BOLD}{YELLOW}Tool permission denied:{RESET}.")
|
|
203
|
+
messages.append({
|
|
204
|
+
"role": "user",
|
|
205
|
+
"content": f"User denied permission for tool {name} and params {params_str}."
|
|
206
|
+
})
|
|
207
|
+
continue
|
|
208
|
+
result = func(**tool_params)
|
|
209
|
+
# Limiting output to 300 characters for readability
|
|
210
|
+
result_str = str(result)
|
|
211
|
+
if len(result_str) > 300:
|
|
212
|
+
result_str = result_str[:300] + "... (truncated to 300 chars)"
|
|
213
|
+
print(f"{BOLD}{MAGENTA}Tool '{name}' returned:{RESET} {result_str}")
|
|
214
|
+
|
|
215
|
+
messages.append({
|
|
216
|
+
"role": "tool",
|
|
217
|
+
"tool_call_id": call_id,
|
|
218
|
+
"content": str(result)
|
|
219
|
+
})
|
|
220
|
+
else:
|
|
221
|
+
print(f"{BOLD}{RED}Warning:{RESET} Unknown tool: {name}, params {params_str}")
|
|
129
222
|
else:
|
|
130
|
-
|
|
223
|
+
messages.append({
|
|
224
|
+
"role": "assistant",
|
|
225
|
+
"content": response_text,
|
|
226
|
+
# "tool_calls": tool_calls
|
|
227
|
+
})
|
|
131
228
|
|
|
132
|
-
if llm_response_flag and llm_tool_response_flag:
|
|
229
|
+
if not llm_response_flag and not llm_tool_response_flag:
|
|
133
230
|
print(f"{BOLD}{RED}Warning:{RESET} No response was given by the LLM.")
|
|
134
231
|
|
|
135
232
|
save_history(messages)
|
mini_code_cli/prompts.py
CHANGED
|
@@ -3,22 +3,30 @@ def system_prompt(tools_dict=None, allowed_dir=None):
|
|
|
3
3
|
"""
|
|
4
4
|
Takes a list of tool names and returns a system prompt string.
|
|
5
5
|
"""
|
|
6
|
-
|
|
7
|
-
if allowed_dir:
|
|
8
|
-
allowed_dir_prompt = f"\nThe path you are allowed to operate in is called: {allowed_dir}"
|
|
6
|
+
|
|
9
7
|
if tools_dict:
|
|
10
8
|
tool_descriptions = "\n".join([f"{name} : {data['description']}" for name, data in tools_dict.items()])
|
|
11
|
-
base_prompt = "You are a helpful AI
|
|
12
|
-
end_prompt =
|
|
9
|
+
base_prompt = "You are a helpful AI assistant with access to the following tools:\n"
|
|
10
|
+
end_prompt = (
|
|
11
|
+
"Choose the most appropriate tool for the task at hand. "
|
|
12
|
+
"If a tool fails or you hit a limit, analyze the error and try a different approach. "
|
|
13
|
+
"Be concise and efficient in your tool usage."
|
|
14
|
+
)
|
|
13
15
|
else:
|
|
14
16
|
tool_descriptions = ""
|
|
15
|
-
base_prompt = "You are a helpful AI
|
|
17
|
+
base_prompt = "You are a helpful AI assistant without any tools."
|
|
16
18
|
end_prompt = ""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
allowed_dir_prompt = ""
|
|
22
|
+
if allowed_dir:
|
|
23
|
+
allowed_dir_prompt = f"\nThe path you are allowed to operate in is called: {allowed_dir}"
|
|
24
|
+
|
|
17
25
|
|
|
18
26
|
prompt = f"""
|
|
19
27
|
{base_prompt}
|
|
20
28
|
{tool_descriptions}{end_prompt}{allowed_dir_prompt}
|
|
21
|
-
|
|
29
|
+
Important: Only follow the user instruction. Do not create additional .md files (unless asked to do so). Do not create unit tests (unless asked to do so), etc.
|
|
22
30
|
"""
|
|
23
31
|
return prompt.strip()
|
|
24
32
|
|
mini_code_cli/tools.py
CHANGED
mini_code_cli/utils.py
CHANGED
|
@@ -19,9 +19,9 @@ def call_openai_server(messages, max_tokens=2048, temperature=0.0, top_p=0.95, m
|
|
|
19
19
|
payload = {
|
|
20
20
|
"model": model,
|
|
21
21
|
"messages": messages,
|
|
22
|
-
"max_tokens": max_tokens,
|
|
23
22
|
"temperature": temperature,
|
|
24
|
-
"
|
|
23
|
+
# "max_tokens": max_tokens,
|
|
24
|
+
# "top_p": top_p
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
if tools:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mini-code-cli
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.7.1
|
|
4
4
|
Summary: Minimal Coding CLI
|
|
5
5
|
Author-email: Nikolai Rozanov <nikolai.rozanov@gmail.com>
|
|
6
6
|
License: The MIT License (MIT)
|
|
@@ -20,7 +20,9 @@ Dynamic: license-file
|
|
|
20
20
|
|
|
21
21
|
# A minimal coding agent cli
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
> No bloat, no dependencies, no tracking...
|
|
24
|
+
|
|
25
|
+
Purpose: A no bloat, minimal coding cli, to enjoy experimenting and researching...
|
|
24
26
|
|
|
25
27
|
## Getting started:
|
|
26
28
|
1. Installation (from pypi)
|
|
@@ -34,5 +36,10 @@ mini-code #run the agent loop
|
|
|
34
36
|
mini-code --url "https://api.deepseek.com" --model "deepseek-v4-flash" --api-key "sk-dummy" --allowed-dir ./experiments/
|
|
35
37
|
```
|
|
36
38
|
|
|
39
|
+
3. Get help:
|
|
40
|
+
```bash
|
|
41
|
+
mini-code --help
|
|
42
|
+
```
|
|
43
|
+
|
|
37
44
|
|
|
38
45
|
## (C) Nikolai Rozanov, 2026 - Present
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
mini_code_cli/__init__.py,sha256=iNgHmKSHOuH99obGjnUMoS2JP4Gw4AOrIfu68EYaZQM,77
|
|
2
|
+
mini_code_cli/agent_loop.py,sha256=yr5XCXkEWfGJsRrA-i1v_IcMQXjkUcCo3AGVYA2RLVY,9317
|
|
3
|
+
mini_code_cli/prompts.py,sha256=1dY5f7pSvfDnjhA-uDmi0Rw66_JFQGfooF699pATO0E,1985
|
|
4
|
+
mini_code_cli/tools.py,sha256=RO51ZJ61NV_R8KqsW9me4-0HPhjgS3DMoKrASLBvYJI,4931
|
|
5
|
+
mini_code_cli/utils.py,sha256=HlmuLH0V4AxZTg0_ZGzlxffqRqs7T3W6FQXaBhioGdk,3647
|
|
6
|
+
mini_code_cli-0.0.7.1.dist-info/licenses/LICENSE,sha256=WCkMINUFi--UtEg_alEE2FBKPtNKkzi7dGzaxzchifc,1088
|
|
7
|
+
mini_code_cli-0.0.7.1.dist-info/METADATA,sha256=9X0f_OMDbx-yGKKzhIa1Ywzsxy35IqMevuU14j-HEqc,2028
|
|
8
|
+
mini_code_cli-0.0.7.1.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
9
|
+
mini_code_cli-0.0.7.1.dist-info/entry_points.txt,sha256=MwDp5UbXpI7wodSmOSYL8_nKutnBiyp96ZDDtaX7x14,60
|
|
10
|
+
mini_code_cli-0.0.7.1.dist-info/top_level.txt,sha256=3Hb6PpNkJYXvzDKXuuFJRvzCdsTRh0TuRWz-ScJ6qV0,14
|
|
11
|
+
mini_code_cli-0.0.7.1.dist-info/RECORD,,
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
mini_code_cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
mini_code_cli/agent_loop.py,sha256=qrrPAdVZY02xAXNYZGb5s_JhdKPgQdXmunC0Is6nidY,5704
|
|
3
|
-
mini_code_cli/prompts.py,sha256=A8nLnYuCWFuge-H5g7hYdRwXJPnRCg4uH__yYq4PzIU,1737
|
|
4
|
-
mini_code_cli/tools.py,sha256=i46l8IvsrXJgqt7r8rKAMTGKNGAGffxKTFlwmAuWoD0,4926
|
|
5
|
-
mini_code_cli/utils.py,sha256=qQk0RurNeWYoyRj-QmQ81Pq1pytJO87SUzm-XzDz9s4,3643
|
|
6
|
-
mini_code_cli-0.0.6.dist-info/licenses/LICENSE,sha256=WCkMINUFi--UtEg_alEE2FBKPtNKkzi7dGzaxzchifc,1088
|
|
7
|
-
mini_code_cli-0.0.6.dist-info/METADATA,sha256=43JAsEkm4zknm4jKtMDyjbTLTIG8d3XPGCSb2KUWRy8,1943
|
|
8
|
-
mini_code_cli-0.0.6.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
9
|
-
mini_code_cli-0.0.6.dist-info/entry_points.txt,sha256=MwDp5UbXpI7wodSmOSYL8_nKutnBiyp96ZDDtaX7x14,60
|
|
10
|
-
mini_code_cli-0.0.6.dist-info/top_level.txt,sha256=3Hb6PpNkJYXvzDKXuuFJRvzCdsTRh0TuRWz-ScJ6qV0,14
|
|
11
|
-
mini_code_cli-0.0.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|