mini-code-cli 0.0.7__tar.gz → 0.0.8.0__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mini-code-cli
3
- Version: 0.0.7
3
+ Version: 0.0.8.0
4
4
  Summary: Minimal Coding CLI
5
5
  Author-email: Nikolai Rozanov <nikolai.rozanov@gmail.com>
6
6
  License: The MIT License (MIT)
@@ -20,18 +20,48 @@ Dynamic: license-file
20
20
 
21
21
  # A minimal coding agent cli
22
22
 
23
- Purpose: no bloat, minimal coding cli, to enjoy experimenting and researching things...
23
+ > No bloat, no dependencies, no tracking, no telemetry...
24
+
25
+ Purpose: A no bloat, minimal coding cli, to enjoy experimenting and researching...
24
26
 
25
27
  ## Getting started:
26
- 1. Installation (from pypi)
28
+
29
+ ### Installation (from pypi)
27
30
  ```bash
28
31
  pip3 install mini-code-cli
29
32
  ```
30
33
 
31
- 2. Run agent: (get LLM running via SGLang or Vllm)
34
+ ### Run agent:
35
+
36
+ 1. Basic usage: (get local LLM running (e.g. via SGLang or Vllm))
37
+ ```bash
38
+ mini-code #run the agent using `localhost:30000/v1` model
39
+ ```
40
+ 2. Custom API based model:
41
+ ```bash
42
+ export MINI_CODE_API_KEY="sk-YOUR_API_KEY"
43
+ mini-code --url "https://api.deepseek.com" --model "deepseek-v4-flash"
44
+ ```
45
+
46
+ Auto-mode, Agent.md, Shell Enabled mode and a specific allowed dir:
47
+ ```bash
48
+ mini-code --enable-shell --auto-mode --agent-md "./skill.md" --allowed-dir "./" --ask-permission --system-prompt "Your awesome new system prompt..."
49
+ ```
50
+ - auto-mode: the agent will continue querying the LLM and executing tools, until no more tool calls are possible
51
+ - agent.md: this will read an agent.md file with the specified name (e.g. ./skill.md)
52
+ - shell-enabled: **Be careful**: This will allow the agent to execute shell commands. Specifically, there is no permission checks.
53
+ - allowed-dir: this specifies which dirs the agent can read and write from, that does not stop the shell comands!
54
+ - ask-permission: asks user permission before every tool call.
55
+ - system-prompt: you can override the default prompt by using a string. (Not Agent.md is always appended after the system prompt.)
56
+
57
+ 3. Try a prompt:
58
+ ```txt
59
+ Write an agent.md file for me that helps an LLM write efficient triton kernels, given a reference implementation.
60
+ ```
61
+
62
+ 4. Get help:
32
63
  ```bash
33
- mini-code #run the agent loop
34
- mini-code --url "https://api.deepseek.com" --model "deepseek-v4-flash" --api-key "sk-dummy" --allowed-dir ./experiments/
64
+ mini-code --help
35
65
  ```
36
66
 
37
67
 
@@ -0,0 +1,48 @@
1
+ # A minimal coding agent cli
2
+
3
+ > No bloat, no dependencies, no tracking, no telemetry...
4
+
5
+ Purpose: A no bloat, minimal coding cli, to enjoy experimenting and researching...
6
+
7
+ ## Getting started:
8
+
9
+ ### Installation (from pypi)
10
+ ```bash
11
+ pip3 install mini-code-cli
12
+ ```
13
+
14
+ ### Run agent:
15
+
16
+ 1. Basic usage: (get local LLM running (e.g. via SGLang or Vllm))
17
+ ```bash
18
+ mini-code #run the agent using `localhost:30000/v1` model
19
+ ```
20
+ 2. Custom API based model:
21
+ ```bash
22
+ export MINI_CODE_API_KEY="sk-YOUR_API_KEY"
23
+ mini-code --url "https://api.deepseek.com" --model "deepseek-v4-flash"
24
+ ```
25
+
26
+ Auto-mode, Agent.md, Shell Enabled mode and a specific allowed dir:
27
+ ```bash
28
+ mini-code --enable-shell --auto-mode --agent-md "./skill.md" --allowed-dir "./" --ask-permission --system-prompt "Your awesome new system prompt..."
29
+ ```
30
+ - auto-mode: the agent will continue querying the LLM and executing tools, until no more tool calls are possible
31
+ - agent.md: this will read an agent.md file with the specified name (e.g. ./skill.md)
32
+ - shell-enabled: **Be careful**: This will allow the agent to execute shell commands. Specifically, there is no permission checks.
33
+ - allowed-dir: this specifies which dirs the agent can read and write from, that does not stop the shell comands!
34
+ - ask-permission: asks user permission before every tool call.
35
+ - system-prompt: you can override the default prompt by using a string. (Not Agent.md is always appended after the system prompt.)
36
+
37
+ 3. Try a prompt:
38
+ ```txt
39
+ Write an agent.md file for me that helps an LLM write efficient triton kernels, given a reference implementation.
40
+ ```
41
+
42
+ 4. Get help:
43
+ ```bash
44
+ mini-code --help
45
+ ```
46
+
47
+
48
+ ## (C) Nikolai Rozanov, 2026 - Present
@@ -0,0 +1,2 @@
1
+ from importlib.metadata import version
2
+ __version__ = version("mini-code-cli")
@@ -0,0 +1,309 @@
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
+
11
+ import os
12
+ import json
13
+ import signal
14
+
15
+
16
+ from . import prompts
17
+ from . import tools
18
+ from . import utils
19
+
20
+ import argparse
21
+ import sys
22
+
23
+ # ANSI escape codes for colors and bold
24
+ BOLD = '\033[1m'
25
+ RED = '\033[91m'
26
+ GREEN = '\033[92m'
27
+ YELLOW = '\033[93m'
28
+ BLUE = '\033[94m'
29
+ MAGENTA = '\033[95m'
30
+ CYAN = '\033[96m'
31
+ RESET = '\033[0m'
32
+
33
+ def save_history(messages):
34
+ # Save messages to history.json
35
+ with open("history.json", "w") as f:
36
+ json.dump(messages, f, indent=2)
37
+
38
+
39
+ def parse_args():
40
+ parser = argparse.ArgumentParser(description="Agent loop for interacting with a language model")
41
+ # MODEL / LLM related
42
+ parser.add_argument("--url", type=str, default="http://0.0.0.0:30000", help="API host")
43
+ parser.add_argument("--max-tokens", type=int, help="Maximum tokens for LLM response")
44
+ parser.add_argument("--temperature", type=float, default=0.0, help="Temperature for LLM")
45
+ parser.add_argument("--model", type=str, default="default", help="Model name")
46
+ parser.add_argument("--api-key", type=str, default=None, help="API key for authentication. If not set, it tries to find it in env variable: MINI_CODE_API_KEY.")
47
+
48
+ # Agent behaviour related
49
+ parser.add_argument("--system-prompt", type=str, help="Replace system prompt, with a custom system prompt.")
50
+ parser.add_argument("--auto-mode", action="store_true", help="Whether to run the agent in `auto-mode'. Or default: `manual-mode'.")
51
+ parser.add_argument("--agent-md", type=str, help="If the agent should use an agent-md file... (it will added after system message.)")
52
+
53
+ # Agent permissions related
54
+ parser.add_argument("--ask-permission", action="store_true", help="Ask for permission before any tool call.")
55
+ parser.add_argument("--allowed-dir", type=str, default=".", help="Allowed directory for file operations")
56
+ parser.add_argument("--enable-shell", action="store_true", help="Allow shell execution. Default: False") #todo
57
+
58
+
59
+ args = parser.parse_args()
60
+ return args
61
+
62
+
63
+ def print_welcome(allowed_dir, server_url, args):
64
+ # Calculate the length of the longest line inside the box
65
+ version = __import__('mini_code_cli').__version__
66
+
67
+ permission_mode = "All tool calls require permission." if args.ask_permission else "Tool calls will execute without asking for permission."
68
+ agent_mode = "Agent is in manual-mode." if not args.auto_mode else "Agent is in auto-mode."
69
+ shell_mode = "Shell disabled." if not args.enable_shell else "Shell enabled."
70
+
71
+ lines = [
72
+ f" MiniCodeCLI (v{version})",
73
+ " Type 'quit' or 'exit' to exit the loop.",
74
+ f" Allowed dir: {allowed_dir}",
75
+ f" Server URL: {server_url}",
76
+ f" {permission_mode}",
77
+ f" {agent_mode}",
78
+ f" {shell_mode}"
79
+ ]
80
+ max_len = max(len(line) for line in lines)
81
+ margin = 2 # Space on each side inside the box
82
+ inner_width = max_len + 2 * margin
83
+ # Build the box
84
+ top_bottom = "╔" + "═" * inner_width + "╗"
85
+ print(top_bottom)
86
+ for line in lines:
87
+ # Pad the line to inner_width characters
88
+ padded = line.ljust(inner_width)
89
+ print(f"║{padded}║")
90
+ bottom = "╚" + "═" * inner_width + "╝"
91
+ print(bottom)
92
+
93
+ def print_help(allowed_dir, server_url, args):
94
+ print_welcome(allowed_dir, server_url, args)
95
+
96
+ help_message = ""
97
+ help_message += "Manual-mode means the agent will ask for user input after each LLM prediction." if not args.auto_mode else "Auto-mode means the agent will keep calling the LLM until no further LLM calls are possible."
98
+
99
+ print(f"{BOLD}{GREEN}HELP:{RESET} {help_message}")
100
+
101
+ def call_function_with_timeout(func, tool_params, name):
102
+ """calls function with params"""
103
+ class TimeoutError(Exception):
104
+ pass
105
+
106
+ def timeout_handler(signum, frame):
107
+ raise TimeoutError("Function call timed out")
108
+
109
+ timeout_seconds = 30 # Set your desired timeout in seconds
110
+ signal.signal(signal.SIGALRM, timeout_handler)
111
+ signal.alarm(timeout_seconds)
112
+ try:
113
+ result = func(**tool_params)
114
+ except TimeoutError:
115
+ print(f"{BOLD}{RED}Error:{RESET} Function {name} timed out after {timeout_seconds} seconds.")
116
+ result = "Error:Function {name} timed out after {timeout_seconds} seconds."
117
+ finally:
118
+ signal.alarm(0) # Disable the alarm
119
+
120
+ return result
121
+
122
+ def main():
123
+ args = parse_args()
124
+
125
+
126
+ # LLM Related
127
+ server_url = f"{args.url}"
128
+ api_key = None
129
+ if args.api_key:
130
+ api_key = args.api_key
131
+ else:
132
+ api_key = os.environ.get("MINI_CODE_API_KEY", "")
133
+ if api_key:
134
+ print(f"{YELLOW}API Key found at MINI_CODE_API_KEY{RESET}")
135
+ else:
136
+ print(f"{YELLOW}NO API Key found at MINI_CODE_API_KEY. Set it with export MINI_CODE_API_KEY='sk-api-key'{RESET}")
137
+
138
+
139
+ # Permissions related
140
+ allowed_dir = os.path.abspath(args.allowed_dir)
141
+
142
+ if not args.enable_shell:
143
+ forbidden_tools = tools.shell_tools
144
+ else:
145
+ forbidden_tools = None
146
+
147
+ # TOOLs
148
+ tools_dict = tools.util_get_tools_dict(forbidden_tools=forbidden_tools)
149
+ llm_tools_dict = tools.util_get_tools(forbidden_tools=forbidden_tools)
150
+
151
+
152
+ # System Message and Agent.md
153
+ if args.system_prompt:
154
+ system_prompt = args.system_prompt
155
+ else:
156
+ system_prompt = prompts.system_prompt(tools_dict=tools_dict, allowed_dir=allowed_dir)
157
+
158
+ messages = [{"role": "system", "content": system_prompt}]
159
+
160
+ if args.agent_md:
161
+ print(f"{YELLOW}AGENT.md '{args.agent_md}' specified.{RESET}")
162
+
163
+ # Check if the file exists
164
+ if not os.path.isfile(args.agent_md):
165
+ print(f"{RED}Error: The AGENT.md '{args.agent_md}' does not exist.{RESET}")
166
+ else:
167
+ # Load the content of agent-md file and add it after system message
168
+ with open(args.agent_md, 'r', encoding='utf-8') as f:
169
+ agent_md_content = f.read()
170
+ print(f"{YELLOW}AGENT.md '{args.agent_md}' loaded.{RESET}")
171
+
172
+ # Append the content as a user message after the system prompt
173
+ messages.append({"role": "user", "content": agent_md_content})
174
+
175
+ print_welcome(allowed_dir, server_url, args)
176
+
177
+ # Agent Logic Flags
178
+ llm_response_flag = False
179
+ llm_tool_response_flag = False
180
+ llm_repeat_flag = False
181
+ tool_count = 0
182
+
183
+ while True:
184
+ if llm_tool_response_flag and args.auto_mode: #skip user input and try to call the model again until there no more tool calls.
185
+ print(f"{BOLD}{YELLOW}System:{RESET} Skipping user input. Continuing with LLM calls until no more tool calls. [Tool count={tool_count}]")
186
+ llm_repeat_flag = True
187
+ else:
188
+ # get user input
189
+ if llm_repeat_flag:
190
+ print(f"{BOLD}{YELLOW}System:{RESET} Called {tool_count} tools in total. Exiting tool loop.")
191
+
192
+ tool_count = 0
193
+ try:
194
+ print("-"*30)
195
+ user_input = input(f"{BOLD}{CYAN}You:{RESET} ")
196
+ if user_input.lower() in ["quit", "exit", "/quit", "/exit"]:
197
+ print("\nThank you. Goodbye! Saving history...")
198
+ save_history(messages)
199
+ break
200
+ except KeyboardInterrupt:
201
+ print("\nThank you. Goodbye! Saving history...")
202
+ save_history(messages)
203
+ break
204
+
205
+ if user_input == "/help":
206
+ print_help(allowed_dir, server_url, args)
207
+ continue
208
+
209
+ messages.append({"role": "user", "content": user_input})
210
+
211
+ llm_response_flag = False
212
+ llm_tool_response_flag = False
213
+
214
+ response_text, tool_calls = utils.call_openai_server(
215
+ messages,
216
+ max_tokens=args.max_tokens,
217
+ temperature=args.temperature,
218
+ model=args.model,
219
+ server_url=server_url,
220
+ tools=llm_tools_dict,
221
+ api_key=api_key,
222
+ )
223
+
224
+ if response_text:
225
+ llm_response_flag = True
226
+ print(f"{BOLD}{GREEN}Assistant:{RESET} {response_text}")
227
+
228
+ if tool_calls:
229
+ llm_tool_response_flag = True
230
+
231
+ messages.append({
232
+ "role": "assistant",
233
+ "content": response_text,
234
+ "tool_calls": tool_calls
235
+ })
236
+ # Tool call parsing and execution
237
+ print(f"{BOLD}{YELLOW}Tool Call:{RESET} LLM is trying to call {len(tool_calls)} tools.")
238
+
239
+ for call in tool_calls:
240
+ tool_count += 1
241
+
242
+ call_id = call.get("id", "")
243
+ name = call.get("function", {}).get("name", "")
244
+ params_str = call.get("function", {}).get("arguments", "{}")
245
+ try:
246
+ tool_params = json.loads(params_str)
247
+ except json.JSONDecodeError:
248
+ print(f"{BOLD}{RED}Warning:{RESET} Error parsing arguments for tool {name}")
249
+ continue
250
+
251
+ if name in tools_dict:
252
+ # Check allowed directory for file operations
253
+ if name in ["read_file", "write_file", "grep", "glob"]:
254
+ filepath = tool_params.get("filepath", "")
255
+ if name == "write_file":
256
+ filepath = tool_params.get("filepath", "")
257
+ if filepath and not os.path.abspath(filepath).startswith(allowed_dir):
258
+ print(f"{BOLD}{RED}Warning:{RESET} File operation not allowed outside allowed directory: {allowed_dir}")
259
+ continue
260
+
261
+ func = tools_dict[name]["function"]
262
+ # asking permissions
263
+ if args.ask_permission:
264
+ print(f"{BOLD}{YELLOW}Tool permission:{RESET} Are you sure you want to call the tool?")
265
+ print(f"name: {name}, parameters: {params_str}")
266
+ try:
267
+ permission_input = input("Type: 'y' or 'yes'\n")
268
+ except KeyboardInterrupt:
269
+ print("\nThank you. Goodbye! Saving history...")
270
+ save_history(messages)
271
+ sys.exit(0)
272
+
273
+ if not permission_input in ['y', 'yes']:
274
+ print(f"{BOLD}{YELLOW}Tool permission denied:{RESET}.")
275
+ messages.append({
276
+ "role": "user",
277
+ "content": f"User denied permission for tool {name} and params {params_str}."
278
+ })
279
+ continue
280
+
281
+ print(f"{BOLD}{YELLOW}Tool Call:{RESET} LLM is trying to call: {name} with {tool_params}.")
282
+ result = call_function_with_timeout(func, tool_params, name)
283
+ # Limiting output to 300 characters for readability
284
+ result_str = str(result)
285
+ if len(result_str) > 300:
286
+ result_str = result_str[:300] + "... (truncated to 300 chars)"
287
+ print(f"{BOLD}{MAGENTA}Tool '{name}' returned:{RESET} {result_str}")
288
+
289
+ messages.append({
290
+ "role": "tool",
291
+ "tool_call_id": call_id,
292
+ "content": str(result)
293
+ })
294
+ else:
295
+ print(f"{BOLD}{RED}Warning:{RESET} Unknown tool: {name}, params {params_str}")
296
+ else:
297
+ messages.append({
298
+ "role": "assistant",
299
+ "content": response_text,
300
+ # "tool_calls": tool_calls
301
+ })
302
+
303
+ if not llm_response_flag and not llm_tool_response_flag:
304
+ print(f"{BOLD}{RED}Warning:{RESET} No response was given by the LLM.")
305
+
306
+ save_history(messages)
307
+
308
+ if __name__ == "__main__":
309
+ main()
@@ -65,14 +65,27 @@ def read_website(url):
65
65
  except Exception as e:
66
66
  return f"Error: {e}"
67
67
 
68
- def util_get_tools(tool_names=None):
68
+ def run_shell_command(command):
69
+ """Run a shell command and return its output. Param: command (string)"""
70
+ try:
71
+ result = subprocess.run(command, shell=True, capture_output=True, text=True, check=False)
72
+ output = result.stdout
73
+ if result.stderr:
74
+ output += "\nSTDERR:\n" + result.stderr
75
+ return output
76
+ except Exception as e:
77
+ return f"Error: {e}"
78
+
79
+ def util_get_tools(allowed_tools=None, forbidden_tools=None):
69
80
  """Return a JSON string describing all tool functions in this file for LLM usage."""
70
81
  import inspect
71
82
  tools = []
72
- if tool_names:
73
- func_names = [name for name, obj in globals().items() if callable(obj) and obj.__module__ == __name__ and not name.startswith('util_') and not name.startswith("_") and name in tool_names]
74
- else:
75
- func_names = [name for name, obj in globals().items() if callable(obj) and obj.__module__ == __name__ and not name.startswith('util_') and not name.startswith("_")]
83
+ func_names = [name for name, obj in globals().items() if callable(obj) and obj.__module__ == __name__ and not name.startswith('util_') and not name.startswith("_")]
84
+
85
+ if allowed_tools:
86
+ func_names = [name for name in func_names if name in allowed_tools]
87
+ if forbidden_tools:
88
+ func_names = [name for name in func_names if name not in forbidden_tools]
76
89
 
77
90
  for name in func_names:
78
91
  func = globals()[name]
@@ -108,17 +121,25 @@ def util_get_tools(tool_names=None):
108
121
  return tools
109
122
 
110
123
 
111
- def util_get_tools_dict():
124
+ def util_get_tools_dict(allowed_tools=None, forbidden_tools=None):
112
125
  """Return a dictionary mapping tool names to their function objects."""
113
126
  result = {}
114
127
  func_names = [name for name, obj in globals().items() if callable(obj) and obj.__module__ == __name__ and not name.startswith('util_') and not name.startswith("_")]
128
+ if allowed_tools:
129
+ func_names = [name for name in func_names if name in allowed_tools]
130
+ if forbidden_tools:
131
+ func_names = [name for name in func_names if name not in forbidden_tools]
132
+
115
133
  for name in func_names:
116
134
  func = globals()[name]
117
135
  desc = func.__doc__ if func.__doc__ else f"Function {name}"
118
136
  result[name] = {"function": func, "description": desc}
119
137
  return result
120
138
 
139
+ shell_tools = [
140
+ "run_shell_command"
141
+ ]
121
142
 
122
143
  if __name__=="__main__":
123
- # print(util_get_tools())
124
- print(json.dumps(tools_dict, indent=4))
144
+ print(util_get_tools())
145
+ # print(json.dumps(tools_dict, indent=4))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mini-code-cli
3
- Version: 0.0.7
3
+ Version: 0.0.8.0
4
4
  Summary: Minimal Coding CLI
5
5
  Author-email: Nikolai Rozanov <nikolai.rozanov@gmail.com>
6
6
  License: The MIT License (MIT)
@@ -20,18 +20,48 @@ Dynamic: license-file
20
20
 
21
21
  # A minimal coding agent cli
22
22
 
23
- Purpose: no bloat, minimal coding cli, to enjoy experimenting and researching things...
23
+ > No bloat, no dependencies, no tracking, no telemetry...
24
+
25
+ Purpose: A no bloat, minimal coding cli, to enjoy experimenting and researching...
24
26
 
25
27
  ## Getting started:
26
- 1. Installation (from pypi)
28
+
29
+ ### Installation (from pypi)
27
30
  ```bash
28
31
  pip3 install mini-code-cli
29
32
  ```
30
33
 
31
- 2. Run agent: (get LLM running via SGLang or Vllm)
34
+ ### Run agent:
35
+
36
+ 1. Basic usage: (get local LLM running (e.g. via SGLang or Vllm))
37
+ ```bash
38
+ mini-code #run the agent using `localhost:30000/v1` model
39
+ ```
40
+ 2. Custom API based model:
41
+ ```bash
42
+ export MINI_CODE_API_KEY="sk-YOUR_API_KEY"
43
+ mini-code --url "https://api.deepseek.com" --model "deepseek-v4-flash"
44
+ ```
45
+
46
+ Auto-mode, Agent.md, Shell Enabled mode and a specific allowed dir:
47
+ ```bash
48
+ mini-code --enable-shell --auto-mode --agent-md "./skill.md" --allowed-dir "./" --ask-permission --system-prompt "Your awesome new system prompt..."
49
+ ```
50
+ - auto-mode: the agent will continue querying the LLM and executing tools, until no more tool calls are possible
51
+ - agent.md: this will read an agent.md file with the specified name (e.g. ./skill.md)
52
+ - shell-enabled: **Be careful**: This will allow the agent to execute shell commands. Specifically, there is no permission checks.
53
+ - allowed-dir: this specifies which dirs the agent can read and write from, that does not stop the shell comands!
54
+ - ask-permission: asks user permission before every tool call.
55
+ - system-prompt: you can override the default prompt by using a string. (Not Agent.md is always appended after the system prompt.)
56
+
57
+ 3. Try a prompt:
58
+ ```txt
59
+ Write an agent.md file for me that helps an LLM write efficient triton kernels, given a reference implementation.
60
+ ```
61
+
62
+ 4. Get help:
32
63
  ```bash
33
- mini-code #run the agent loop
34
- mini-code --url "https://api.deepseek.com" --model "deepseek-v4-flash" --api-key "sk-dummy" --allowed-dir ./experiments/
64
+ mini-code --help
35
65
  ```
36
66
 
37
67
 
@@ -1,6 +1,7 @@
1
1
  [project]
2
2
  name = "mini-code-cli"
3
- version = "0.0.7"
3
+ # version = { file = "mini_code_cli/__init__.py", attr = "__version__" }
4
+ version = "0.0.8.0"
4
5
  description = "Minimal Coding CLI"
5
6
  readme = "README.md"
6
7
  requires-python = ">=3.10"
@@ -1,18 +0,0 @@
1
- # A minimal coding agent cli
2
-
3
- Purpose: no bloat, minimal coding cli, to enjoy experimenting and researching things...
4
-
5
- ## Getting started:
6
- 1. Installation (from pypi)
7
- ```bash
8
- pip3 install mini-code-cli
9
- ```
10
-
11
- 2. Run agent: (get LLM running via SGLang or Vllm)
12
- ```bash
13
- mini-code #run the agent loop
14
- mini-code --url "https://api.deepseek.com" --model "deepseek-v4-flash" --api-key "sk-dummy" --allowed-dir ./experiments/
15
- ```
16
-
17
-
18
- ## (C) Nikolai Rozanov, 2026 - Present
File without changes
@@ -1,158 +0,0 @@
1
- import os
2
- import json
3
-
4
- from . import prompts
5
- from . import tools
6
- from . import utils
7
-
8
- import argparse
9
- import sys
10
-
11
- # ANSI escape codes for colors and bold
12
- BOLD = '\033[1m'
13
- RED = '\033[91m'
14
- GREEN = '\033[92m'
15
- YELLOW = '\033[93m'
16
- BLUE = '\033[94m'
17
- MAGENTA = '\033[95m'
18
- CYAN = '\033[96m'
19
- RESET = '\033[0m'
20
-
21
- def save_history(messages):
22
- # Save messages to history.json
23
- with open("history.json", "w") as f:
24
- json.dump(messages, f, indent=2)
25
-
26
-
27
- def parse_args():
28
- parser = argparse.ArgumentParser(description="Agent loop for interacting with a language model")
29
- parser.add_argument("--url", type=str, default="http://0.0.0.0:30000", help="API host")
30
- parser.add_argument("--system_prompt", type=str, help="API host")
31
- parser.add_argument("--api-key", type=str, default=None, help="API key for authentication")
32
- parser.add_argument("--allowed-dir", type=str, default=".", help="Allowed directory for file operations")
33
- parser.add_argument("--max-tokens", type=int, help="Maximum tokens for LLM response")
34
- parser.add_argument("--temperature", type=float, default=0.0, help="Temperature for LLM")
35
- parser.add_argument("--model", type=str, default="default", help="Model name")
36
- args = parser.parse_args()
37
- return args
38
-
39
- def print_welcome(allowed_dir, server_url):
40
- # Calculate the length of the longest line inside the box
41
- lines = [
42
- " Coding Agent Loop Started",
43
- " Type 'quit' or 'exit' to exit the loop.",
44
- f" Allowed dir: {allowed_dir}",
45
- f" Server URL: {server_url}"
46
- ]
47
- max_len = max(len(line) for line in lines)
48
- margin = 2 # Space on each side inside the box
49
- inner_width = max_len + 2 * margin
50
- # Build the box
51
- top_bottom = "╔" + "═" * inner_width + "╗"
52
- print(top_bottom)
53
- for line in lines:
54
- # Pad the line to inner_width characters
55
- padded = line.ljust(inner_width)
56
- print(f"║{padded}║")
57
- bottom = "╚" + "═" * inner_width + "╝"
58
- print(bottom)
59
-
60
- def main():
61
- args = parse_args()
62
-
63
- allowed_dir = os.path.abspath(args.allowed_dir)
64
- server_url = f"{args.url}"
65
-
66
- tools_dict = tools.util_get_tools_dict()
67
- llm_tools_dict = tools.util_get_tools()
68
-
69
- if args.system_prompt:
70
- system_prompt = args.system_prompt
71
- else:
72
- system_prompt = prompts.system_prompt(tools_dict=tools_dict, allowed_dir=allowed_dir)
73
-
74
- messages = [{"role": "system", "content": system_prompt}]
75
-
76
- print_welcome(allowed_dir, server_url)
77
-
78
- while True:
79
- llm_response_flag = False
80
- llm_tool_response_flag = False
81
- try:
82
- print("-"*30)
83
- user_input = input(f"{BOLD}{CYAN}You:{RESET} ")
84
- if user_input.lower() in ["quit", "exit"]:
85
- print("\nThank you. Goodbye! Saving history...")
86
- save_history(messages)
87
- break
88
- except KeyboardInterrupt:
89
- print("\nThank you. Goodbye! Saving history...")
90
- save_history(messages)
91
- break
92
-
93
- messages.append({"role": "user", "content": user_input})
94
-
95
- response_text, tool_calls = utils.call_openai_server(
96
- messages,
97
- max_tokens=args.max_tokens,
98
- temperature=args.temperature,
99
- model=args.model,
100
- server_url=server_url,
101
- tools=llm_tools_dict
102
- )
103
-
104
- if response_text:
105
- print(f"{BOLD}{GREEN}Assistant:{RESET} {response_text}")
106
- messages.append({"role": "assistant", "content": response_text})
107
- else:
108
- llm_response_flag = True
109
-
110
- if tool_calls:
111
- # Tool call parsing and execution
112
- print(f"{BOLD}{YELLOW}Tool Call:{RESET} LLM is trying to call {len(tool_calls)} tools.")
113
-
114
- for call in tool_calls:
115
- call_id = call.get("id", "")
116
- name = call.get("function", {}).get("name", "")
117
- params_str = call.get("function", {}).get("arguments", "{}")
118
- try:
119
- tool_params = json.loads(params_str)
120
- except json.JSONDecodeError:
121
- print(f"{BOLD}{RED}Warning:{RESET} Error parsing arguments for tool {name}")
122
- continue
123
-
124
- if name in tools_dict:
125
- # Check allowed directory for file operations
126
- if name in ["read_file", "write_file", "grep", "glob"]:
127
- filepath = tool_params.get("filepath", "")
128
- if name == "write_file":
129
- filepath = tool_params.get("filepath", "")
130
- if filepath and not os.path.abspath(filepath).startswith(allowed_dir):
131
- print(f"{BOLD}{RED}Warning:{RESET} File operation not allowed outside allowed directory: {allowed_dir}")
132
- continue
133
-
134
- func = tools_dict[name]["function"]
135
- result = func(**tool_params)
136
- # Limiting output to 300 characters for readability
137
- result_str = str(result)
138
- if len(result_str) > 300:
139
- result_str = result_str[:300] + "... (truncated to 300 chars)"
140
- print(f"{BOLD}{MAGENTA}Tool '{name}' returned:{RESET} {result_str}")
141
-
142
- messages.append({
143
- "role": "tool",
144
- "tool_call_id": call_id,
145
- "content": str(result)
146
- })
147
- else:
148
- print(f"{BOLD}{RED}Warning:{RESET} Unknown tool: {name}")
149
- else:
150
- llm_tool_response_flag = True
151
-
152
- if llm_response_flag and llm_tool_response_flag:
153
- print(f"{BOLD}{RED}Warning:{RESET} No response was given by the LLM.")
154
-
155
- save_history(messages)
156
-
157
- if __name__ == "__main__":
158
- main()
File without changes
File without changes