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 CHANGED
@@ -0,0 +1,2 @@
1
+ from importlib.metadata import version
2
+ __version__ = version("mini-code-cli")
@@ -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, default=2048, help="Maximum tokens for LLM response")
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
- system_prompt = prompts.system_prompt(tools_dict=tools_dict, allowed_dir=allowed_dir)
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
- 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
- """)
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
- 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"]:
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
- except KeyboardInterrupt:
69
- print("\nThank you. Goodbye! Saving history...")
70
- save_history(messages)
71
- break
133
+
134
+ if user_input == "/help":
135
+ print_help(allowed_dir, server_url, args)
136
+ continue
72
137
 
73
- messages.append({"role": "user", "content": user_input})
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
- 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"]:
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
- 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}")
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
- llm_tool_response_flag = True
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
- 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
 
mini_code_cli/tools.py CHANGED
@@ -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)
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
- "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
@@ -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,,