mini-code-cli 0.0.6__tar.gz → 0.0.7.1__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.6
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
- Purpose: no bloat, minimal coding cli, to enjoy experimenting and researching things...
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
@@ -1,6 +1,8 @@
1
1
  # A minimal coding agent cli
2
2
 
3
- Purpose: no bloat, minimal coding cli, to enjoy experimenting and researching things...
3
+ > No bloat, no dependencies, no tracking...
4
+
5
+ Purpose: A no bloat, minimal coding cli, to enjoy experimenting and researching...
4
6
 
5
7
  ## Getting started:
6
8
  1. Installation (from pypi)
@@ -14,5 +16,10 @@ mini-code #run the agent loop
14
16
  mini-code --url "https://api.deepseek.com" --model "deepseek-v4-flash" --api-key "sk-dummy" --allowed-dir ./experiments/
15
17
  ```
16
18
 
19
+ 3. Get help:
20
+ ```bash
21
+ mini-code --help
22
+ ```
23
+
17
24
 
18
25
  ## (C) Nikolai Rozanov, 2026 - Present
@@ -0,0 +1,2 @@
1
+ from importlib.metadata import version
2
+ __version__ = version("mini-code-cli")
@@ -0,0 +1,235 @@
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
+
14
+ from . import prompts
15
+ from . import tools
16
+ from . import utils
17
+
18
+ import argparse
19
+ import sys
20
+
21
+ # ANSI escape codes for colors and bold
22
+ BOLD = '\033[1m'
23
+ RED = '\033[91m'
24
+ GREEN = '\033[92m'
25
+ YELLOW = '\033[93m'
26
+ BLUE = '\033[94m'
27
+ MAGENTA = '\033[95m'
28
+ CYAN = '\033[96m'
29
+ RESET = '\033[0m'
30
+
31
+ def save_history(messages):
32
+ # Save messages to history.json
33
+ with open("history.json", "w") as f:
34
+ json.dump(messages, f, indent=2)
35
+
36
+
37
+ def parse_args():
38
+ parser = argparse.ArgumentParser(description="Agent loop for interacting with a language model")
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")
41
+ parser.add_argument("--api-key", type=str, default=None, help="API key for authentication")
42
+ parser.add_argument("--allowed-dir", type=str, default=".", help="Allowed directory for file operations")
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("--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
+
49
+ args = parser.parse_args()
50
+ return args
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
+
89
+ def main():
90
+ args = parse_args()
91
+
92
+ allowed_dir = os.path.abspath(args.allowed_dir)
93
+ server_url = f"{args.url}"
94
+
95
+ tools_dict = tools.util_get_tools_dict()
96
+ llm_tools_dict = tools.util_get_tools()
97
+
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)
102
+
103
+ messages = [{"role": "system", "content": system_prompt}]
104
+
105
+ print_welcome(allowed_dir, server_url, args)
106
+
107
+ llm_response_flag = False
108
+ llm_tool_response_flag = False
109
+ llm_repeat_flag = False
110
+ tool_count = 0
111
+
112
+ while True:
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:
130
+ print("\nThank you. Goodbye! Saving history...")
131
+ save_history(messages)
132
+ break
133
+
134
+ if user_input == "/help":
135
+ print_help(allowed_dir, server_url, args)
136
+ continue
137
+
138
+ messages.append({"role": "user", "content": user_input})
139
+
140
+ llm_response_flag = False
141
+ llm_tool_response_flag = False
142
+
143
+ response_text, tool_calls = utils.call_openai_server(
144
+ messages,
145
+ max_tokens=args.max_tokens,
146
+ temperature=args.temperature,
147
+ model=args.model,
148
+ server_url=server_url,
149
+ tools=llm_tools_dict
150
+ )
151
+
152
+ if response_text:
153
+ llm_response_flag = True
154
+ print(f"{BOLD}{GREEN}Assistant:{RESET} {response_text}")
155
+
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
+ })
164
+ # Tool call parsing and execution
165
+ print(f"{BOLD}{YELLOW}Tool Call:{RESET} LLM is trying to call {len(tool_calls)} tools.")
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":
184
+ filepath = tool_params.get("filepath", "")
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}")
222
+ else:
223
+ messages.append({
224
+ "role": "assistant",
225
+ "content": response_text,
226
+ # "tool_calls": tool_calls
227
+ })
228
+
229
+ if not llm_response_flag and not llm_tool_response_flag:
230
+ print(f"{BOLD}{RED}Warning:{RESET} No response was given by the LLM.")
231
+
232
+ save_history(messages)
233
+
234
+ if __name__ == "__main__":
235
+ main()
@@ -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
- allowed_dir_prompt = ""
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 coding assistant with access to the following tools:\n"
12
- end_prompt = "\n\nWhen answering, first use available tools if needed, then provide a compact summary of the results."
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 coding assistant without any tools."
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
- Keep responses concise and focused.
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
 
@@ -31,7 +31,7 @@ def grep(pattern, filepath):
31
31
  except Exception as e:
32
32
  return f"Error: {e}"
33
33
 
34
- def glob(pattern):
34
+ def glob(pattern="./"):
35
35
  """Return list of file paths matching the given glob pattern. Param: pattern"""
36
36
  try:
37
37
  return glob_module.glob(pattern)
@@ -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
- "top_p": top_p
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.6
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
- Purpose: no bloat, minimal coding cli, to enjoy experimenting and researching things...
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
@@ -1,6 +1,7 @@
1
1
  [project]
2
2
  name = "mini-code-cli"
3
- version = "0.0.6"
3
+ # version = { file = "mini_code_cli/__init__.py", attr = "__version__" }
4
+ version = "0.0.7.1"
4
5
  description = "Minimal Coding CLI"
5
6
  readme = "README.md"
6
7
  requires-python = ">=3.10"
File without changes
@@ -1,138 +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
- def parse_args():
27
- parser = argparse.ArgumentParser(description="Agent loop for interacting with a language model")
28
- parser.add_argument("--url", type=str, default="http://0.0.0.0:30000", help="API host")
29
- parser.add_argument("--api-key", type=str, default=None, help="API key for authentication")
30
- parser.add_argument("--allowed-dir", type=str, default=".", help="Allowed directory for file operations")
31
- parser.add_argument("--max-tokens", type=int, default=2048, help="Maximum tokens for LLM response")
32
- parser.add_argument("--temperature", type=float, default=0.0, help="Temperature for LLM")
33
- parser.add_argument("--model", type=str, default="default", help="Model name")
34
- args = parser.parse_args()
35
- return args
36
-
37
- def main():
38
- args = parse_args()
39
-
40
- allowed_dir = os.path.abspath(args.allowed_dir)
41
- server_url = f"{args.url}"
42
-
43
- tools_dict = tools.util_get_tools_dict()
44
- llm_tools_dict = tools.util_get_tools()
45
-
46
- system_prompt = prompts.system_prompt(tools_dict=tools_dict, allowed_dir=allowed_dir)
47
-
48
- messages = [{"role": "system", "content": system_prompt}]
49
-
50
- print(f"""
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
- """)
57
-
58
- while True:
59
- llm_response_flag = False
60
- llm_tool_response_flag = False
61
- try:
62
- print("-"*30)
63
- user_input = input(f"{BOLD}{CYAN}You:{RESET} ")
64
- if user_input.lower() in ["quit", "exit"]:
65
- print("\nThank you. Goodbye! Saving history...")
66
- save_history(messages)
67
- break
68
- except KeyboardInterrupt:
69
- print("\nThank you. Goodbye! Saving history...")
70
- save_history(messages)
71
- break
72
-
73
- messages.append({"role": "user", "content": user_input})
74
-
75
- response_text, tool_calls = utils.call_openai_server(
76
- messages,
77
- max_tokens=args.max_tokens,
78
- temperature=args.temperature,
79
- model=args.model,
80
- server_url=server_url,
81
- tools=llm_tools_dict
82
- )
83
-
84
- if response_text:
85
- print(f"{BOLD}{GREEN}Assistant:{RESET} {response_text}")
86
- messages.append({"role": "assistant", "content": response_text})
87
- else:
88
- llm_response_flag = True
89
-
90
- if tool_calls:
91
- # Tool call parsing and execution
92
- print(f"{BOLD}{YELLOW}Tool Call:{RESET} LLM is trying to call {len(tool_calls)} tools.")
93
- if tool_calls:
94
- for call in tool_calls:
95
- call_id = call.get("id", "")
96
- name = call.get("function", {}).get("name", "")
97
- params_str = call.get("function", {}).get("arguments", "{}")
98
- try:
99
- tool_params = json.loads(params_str)
100
- except json.JSONDecodeError:
101
- print(f"{BOLD}{RED}Warning:{RESET} Error parsing arguments for tool {name}")
102
- continue
103
-
104
- if name in tools_dict:
105
- # Check allowed directory for file operations
106
- if name in ["read_file", "write_file", "grep", "glob"]:
107
- filepath = tool_params.get("filepath", "")
108
- if name == "write_file":
109
- filepath = tool_params.get("filepath", "")
110
- if filepath and not os.path.abspath(filepath).startswith(allowed_dir):
111
- print(f"{BOLD}{RED}Warning:{RESET} File operation not allowed outside allowed directory: {allowed_dir}")
112
- continue
113
-
114
- func = tools_dict[name]["function"]
115
- result = func(**tool_params)
116
- # Limiting output to 300 characters for readability
117
- result_str = str(result)
118
- if len(result_str) > 300:
119
- result_str = result_str[:300] + "... (truncated to 300 chars)"
120
- print(f"{BOLD}{MAGENTA}Tool '{name}' returned:{RESET} {result_str}")
121
-
122
- messages.append({
123
- "role": "tool",
124
- "tool_call_id": call_id,
125
- "content": str(result)
126
- })
127
- else:
128
- print(f"{BOLD}{RED}Warning:{RESET} Unknown tool: {name}")
129
- else:
130
- llm_tool_response_flag = True
131
-
132
- if llm_response_flag and llm_tool_response_flag:
133
- print(f"{BOLD}{RED}Warning:{RESET} No response was given by the LLM.")
134
-
135
- save_history(messages)
136
-
137
- if __name__ == "__main__":
138
- main()
File without changes
File without changes