mini-code-cli 0.0.8.0__py3-none-any.whl → 0.0.8.2__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.
@@ -12,13 +12,16 @@ import os
12
12
  import json
13
13
  import signal
14
14
 
15
+ import time
16
+ import uuid
17
+
18
+ import argparse
19
+ import sys
15
20
 
16
21
  from . import prompts
17
22
  from . import tools
18
23
  from . import utils
19
24
 
20
- import argparse
21
- import sys
22
25
 
23
26
  # ANSI escape codes for colors and bold
24
27
  BOLD = '\033[1m'
@@ -30,11 +33,21 @@ MAGENTA = '\033[95m'
30
33
  CYAN = '\033[96m'
31
34
  RESET = '\033[0m'
32
35
 
33
- def save_history(messages):
36
+ def save_history(messages, args, session_name=""):
34
37
  # Save messages to history.json
35
- with open("history.json", "w") as f:
38
+ if args.cache_dir:
39
+ cache_dir = args.cache_dir
40
+ else:
41
+ cache_dir = os.path.expanduser("~/.cache/mini_code/sessions/")
42
+ os.makedirs(cache_dir, exist_ok=True)
43
+ history_path = os.path.join(cache_dir, f"history_{session_name}.json")
44
+ with open(history_path, 'w') as f:
36
45
  json.dump(messages, f, indent=2)
37
46
 
47
+ def generate_unique_id():
48
+ """Generate a unique ID based on current time and a random component."""
49
+ timestamp = int(time.time() * 1000000) # microseconds
50
+ return f"{timestamp}-{uuid.uuid4().hex[:8]}"
38
51
 
39
52
  def parse_args():
40
53
  parser = argparse.ArgumentParser(description="Agent loop for interacting with a language model")
@@ -45,10 +58,14 @@ def parse_args():
45
58
  parser.add_argument("--model", type=str, default="default", help="Model name")
46
59
  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
60
 
61
+ # Other settings:
62
+ parser.add_argument("--cache-dir", type=str, help="The cache dir for the session histories.")
63
+
48
64
  # Agent behaviour related
49
65
  parser.add_argument("--system-prompt", type=str, help="Replace system prompt, with a custom system prompt.")
50
66
  parser.add_argument("--auto-mode", action="store_true", help="Whether to run the agent in `auto-mode'. Or default: `manual-mode'.")
51
67
  parser.add_argument("--agent-md", type=str, help="If the agent should use an agent-md file... (it will added after system message.)")
68
+ parser.add_argument("--prompt", type=str, help="An initial prompt from the user...")
52
69
 
53
70
  # Agent permissions related
54
71
  parser.add_argument("--ask-permission", action="store_true", help="Ask for permission before any tool call.")
@@ -60,7 +77,7 @@ def parse_args():
60
77
  return args
61
78
 
62
79
 
63
- def print_welcome(allowed_dir, server_url, args):
80
+ def print_welcome(allowed_dir, server_url, args, unique_id, cache_dir):
64
81
  # Calculate the length of the longest line inside the box
65
82
  version = __import__('mini_code_cli').__version__
66
83
 
@@ -70,6 +87,8 @@ def print_welcome(allowed_dir, server_url, args):
70
87
 
71
88
  lines = [
72
89
  f" MiniCodeCLI (v{version})",
90
+ f" The session-id is: {unique_id}" ,
91
+ f" It will be saved under: `{cache_dir}`",
73
92
  " Type 'quit' or 'exit' to exit the loop.",
74
93
  f" Allowed dir: {allowed_dir}",
75
94
  f" Server URL: {server_url}",
@@ -90,8 +109,8 @@ def print_welcome(allowed_dir, server_url, args):
90
109
  bottom = "╚" + "═" * inner_width + "╝"
91
110
  print(bottom)
92
111
 
93
- def print_help(allowed_dir, server_url, args):
94
- print_welcome(allowed_dir, server_url, args)
112
+ def print_help(allowed_dir, server_url, args, unique_id, cache_dir):
113
+ print_welcome(allowed_dir, server_url, args, unique_id, cache_dir)
95
114
 
96
115
  help_message = ""
97
116
  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."
@@ -119,9 +138,14 @@ def call_function_with_timeout(func, tool_params, name):
119
138
 
120
139
  return result
121
140
 
141
+
142
+
122
143
  def main():
123
144
  args = parse_args()
124
145
 
146
+ # Get session unique ID:
147
+ unique_id = generate_unique_id()
148
+ cache_dir = args.cache_dir if args.cache_dir else "~/.cache/mini_code/sessions/"
125
149
 
126
150
  # LLM Related
127
151
  server_url = f"{args.url}"
@@ -172,7 +196,7 @@ def main():
172
196
  # Append the content as a user message after the system prompt
173
197
  messages.append({"role": "user", "content": agent_md_content})
174
198
 
175
- print_welcome(allowed_dir, server_url, args)
199
+ print_welcome(allowed_dir, server_url, args, unique_id, cache_dir)
176
200
 
177
201
  # Agent Logic Flags
178
202
  llm_response_flag = False
@@ -180,130 +204,157 @@ def main():
180
204
  llm_repeat_flag = False
181
205
  tool_count = 0
182
206
 
207
+ user_input = args.prompt
208
+
183
209
  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}")
210
+ try:
211
+
212
+ 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.
213
+ print(f"{BOLD}{YELLOW}System:{RESET} Skipping user input. Continuing with LLM calls until no more tool calls. [Tool count={tool_count}]")
214
+ llm_repeat_flag = True
215
+ else:
216
+ # get user input
217
+ if llm_repeat_flag:
218
+ print(f"{BOLD}{YELLOW}System:{RESET} Called {tool_count} tools in total. Exiting tool loop.")
219
+
220
+ tool_count = 0
221
+ if not user_input:
222
+ try:
223
+ print("-"*30)
224
+ user_input = input(f"{BOLD}{CYAN}You:{RESET} ")
225
+ if user_input.lower() in ["quit", "exit", "/quit", "/exit"]:
226
+ print("\nThank you. Goodbye!")
227
+ print(f"Saving history to: {cache_dir}/{unique_id}")
228
+ save_history(messages, args, unique_id)
229
+ break
230
+ except KeyboardInterrupt:
231
+ print("\nThank you. Goodbye!")
232
+ print(f"Saving history to: {cache_dir}/{unique_id}")
233
+ save_history(messages, args, unique_id)
234
+ break
235
+ else:
236
+ print("-"*30)
237
+ print(f"{BOLD}{CYAN}You (preset): {RESET}{user_input}")
238
+
239
+ if user_input == "/help":
240
+ print_help(allowed_dir, server_url, args, unique_id, cache_dir)
241
+ user_input = ""
249
242
  continue
250
243
 
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":
244
+ messages.append({"role": "user", "content": user_input})
245
+ user_input = ""
246
+
247
+ llm_response_flag = False
248
+ llm_tool_response_flag = False
249
+
250
+ response_text, tool_calls = utils.call_openai_server(
251
+ messages,
252
+ max_tokens=args.max_tokens,
253
+ temperature=args.temperature,
254
+ model=args.model,
255
+ server_url=server_url,
256
+ tools=llm_tools_dict,
257
+ api_key=api_key,
258
+ )
259
+
260
+ if response_text:
261
+ llm_response_flag = True
262
+ print(f"{BOLD}{GREEN}Assistant:{RESET} {response_text}")
263
+
264
+ if tool_calls:
265
+ llm_tool_response_flag = True
266
+
267
+ messages.append({
268
+ "role": "assistant",
269
+ "content": response_text,
270
+ "tool_calls": tool_calls
271
+ })
272
+ # Tool call parsing and execution
273
+ print(f"{BOLD}{YELLOW}Tool Call:{RESET} LLM is trying to call {len(tool_calls)} tools.")
274
+
275
+ for call in tool_calls:
276
+ tool_count += 1
277
+
278
+ call_id = call.get("id", "")
279
+ name = call.get("function", {}).get("name", "")
280
+ params_str = call.get("function", {}).get("arguments", "{}")
281
+ try:
282
+ tool_params = json.loads(params_str)
283
+ except json.JSONDecodeError:
284
+ print(f"{BOLD}{RED}Warning:{RESET} Error parsing arguments for tool {name}")
285
+ continue
286
+
287
+ if name in tools_dict:
288
+ # Check allowed directory for file operations
289
+ if name in ["read_file", "write_file", "grep", "glob"]:
256
290
  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.")
291
+ if name == "write_file":
292
+ filepath = tool_params.get("filepath", "")
293
+ if filepath and not os.path.abspath(filepath).startswith(allowed_dir):
294
+ print(f"{BOLD}{RED}Warning:{RESET} File operation not allowed outside allowed directory: {allowed_dir}")
295
+ continue
296
+
297
+ func = tools_dict[name]["function"]
298
+ # asking permissions
299
+ if args.ask_permission:
300
+ print(f"{BOLD}{YELLOW}Tool permission:{RESET} Are you sure you want to call the tool?")
301
+ print(f"name: {name}, parameters: {params_str}")
302
+ try:
303
+ permission_input = input("Type: 'y' or 'yes'\n")
304
+ except KeyboardInterrupt:
305
+ print("\nThank you. Goodbye! Saving history...")
306
+ save_history(messages, args, unique_id)
307
+ sys.exit(0)
308
+
309
+ if not permission_input in ['y', 'yes']:
310
+ print(f"{BOLD}{YELLOW}Tool permission denied:{RESET}.")
311
+ messages.append({
312
+ "role": "user",
313
+ "content": f"User denied permission for tool {name} and params {params_str}."
314
+ })
315
+ continue
316
+
317
+ print(f"{BOLD}{YELLOW}Tool Call:{RESET} LLM is trying to call: {name} with {tool_params}.")
318
+ result = call_function_with_timeout(func, tool_params, name)
319
+ # Limiting output to 300 characters for readability
320
+ result_str = str(result)
321
+ if len(result_str) > 300:
322
+ result_str = result_str[:300] + "... (truncated to 300 chars)"
323
+ print(f"{BOLD}{MAGENTA}Tool '{name}' returned:{RESET} {result_str}")
324
+
325
+ messages.append({
326
+ "role": "tool",
327
+ "tool_call_id": call_id,
328
+ "content": str(result)
329
+ })
330
+ else:
331
+ print(f"{BOLD}{RED}Error:{RESET} Unknown tool: {name}, params {params_str}")
332
+ messages.append({
333
+ "role": "tool",
334
+ "tool_call_id": call_id,
335
+ "content": f"Error: Unknown tool: {name}, params {params_str}"
336
+ })
337
+ else:
338
+ messages.append({
339
+ "role": "assistant",
340
+ "content": response_text,
341
+ # "tool_calls": tool_calls
342
+ })
343
+
344
+ if not llm_response_flag and not llm_tool_response_flag:
345
+ print(f"{BOLD}{RED}Warning:{RESET} No response was given by the LLM.")
346
+
347
+ save_history(messages, args, unique_id)
305
348
 
306
- save_history(messages)
349
+ except KeyboardInterrupt:
350
+ print(f"{BOLD}{RED}INTERRUPTING:{RESET} Mini Code Exiting...")
351
+
352
+ except Exception as e:
353
+ print(f"{BOLD}{RED}FATAL ERROR:{RESET} Mini Code Crashed...:\n{e}")
354
+
355
+ finally:
356
+ print(f"Saving history to: {cache_dir}/{unique_id}")
357
+ save_history(messages, args, unique_id)
307
358
 
308
359
  if __name__ == "__main__":
309
360
  main()
mini_code_cli/prompts.py CHANGED
@@ -6,7 +6,7 @@ def system_prompt(tools_dict=None, allowed_dir=None):
6
6
 
7
7
  if tools_dict:
8
8
  tool_descriptions = "\n".join([f"{name} : {data['description']}" for name, data in tools_dict.items()])
9
- base_prompt = "You are a helpful AI assistant with access to the following tools:\n"
9
+ base_prompt = "You are a helpful AI assistant with access to the following tools (only):\n"
10
10
  end_prompt = (
11
11
  "Choose the most appropriate tool for the task at hand. "
12
12
  "If a tool fails or you hit a limit, analyze the error and try a different approach. "
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mini-code-cli
3
- Version: 0.0.8.0
3
+ Version: 0.0.8.2
4
4
  Summary: Minimal Coding CLI
5
5
  Author-email: Nikolai Rozanov <nikolai.rozanov@gmail.com>
6
6
  License: The MIT License (MIT)
@@ -63,6 +63,30 @@ Write an agent.md file for me that helps an LLM write efficient triton kernels,
63
63
  ```bash
64
64
  mini-code --help
65
65
  ```
66
+ ```
67
+ Agent loop for interacting with a language model
68
+
69
+ options:
70
+ -h, --help show this help message and exit
71
+ --url URL API host
72
+ --max-tokens MAX_TOKENS
73
+ Maximum tokens for LLM response
74
+ --temperature TEMPERATURE
75
+ Temperature for LLM
76
+ --model MODEL Model name
77
+ --api-key API_KEY API key for authentication. If not set, it tries to find it in env variable: MINI_CODE_API_KEY.
78
+ --cache-dir CACHE_DIR
79
+ The cache dir for the session histories.
80
+ --system-prompt SYSTEM_PROMPT
81
+ Replace system prompt, with a custom system prompt.
82
+ --auto-mode Whether to run the agent in `auto-mode'. Or default: `manual-mode'.
83
+ --agent-md AGENT_MD If the agent should use an agent-md file... (it will added after system message.)
84
+ --prompt PROMPT An initial prompt from the user...
85
+ --ask-permission Ask for permission before any tool call.
86
+ --allowed-dir ALLOWED_DIR
87
+ Allowed directory for file operations
88
+ --enable-shell Allow shell execution. Default: False
89
+ ```
66
90
 
67
91
 
68
92
  ## (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=V74UYHU9y4ZYV_OtYkDpFWJnuEgmTRP36Uxc4AP89y0,14783
3
+ mini_code_cli/prompts.py,sha256=hIX-A_F7RBuTAAwYaLaDA6JMC1lhv9Fkx1WkS5v83TU,1992
4
+ mini_code_cli/tools.py,sha256=R8DWRu0zij2Xwioldd6tDeMtVfSaZhqcWAsjvskOxvw,5627
5
+ mini_code_cli/utils.py,sha256=HlmuLH0V4AxZTg0_ZGzlxffqRqs7T3W6FQXaBhioGdk,3647
6
+ mini_code_cli-0.0.8.2.dist-info/licenses/LICENSE,sha256=WCkMINUFi--UtEg_alEE2FBKPtNKkzi7dGzaxzchifc,1088
7
+ mini_code_cli-0.0.8.2.dist-info/METADATA,sha256=GpP0D_izk9dRFw7i3qUzV_2AatFQPsBceunIU1qmFbc,4284
8
+ mini_code_cli-0.0.8.2.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
9
+ mini_code_cli-0.0.8.2.dist-info/entry_points.txt,sha256=MwDp5UbXpI7wodSmOSYL8_nKutnBiyp96ZDDtaX7x14,60
10
+ mini_code_cli-0.0.8.2.dist-info/top_level.txt,sha256=3Hb6PpNkJYXvzDKXuuFJRvzCdsTRh0TuRWz-ScJ6qV0,14
11
+ mini_code_cli-0.0.8.2.dist-info/RECORD,,
@@ -1,11 +0,0 @@
1
- mini_code_cli/__init__.py,sha256=iNgHmKSHOuH99obGjnUMoS2JP4Gw4AOrIfu68EYaZQM,77
2
- mini_code_cli/agent_loop.py,sha256=m2X-53-0oM7ZJTZRzXHOurofcqdUXkr5W0xiRKXkZkg,12130
3
- mini_code_cli/prompts.py,sha256=1dY5f7pSvfDnjhA-uDmi0Rw66_JFQGfooF699pATO0E,1985
4
- mini_code_cli/tools.py,sha256=R8DWRu0zij2Xwioldd6tDeMtVfSaZhqcWAsjvskOxvw,5627
5
- mini_code_cli/utils.py,sha256=HlmuLH0V4AxZTg0_ZGzlxffqRqs7T3W6FQXaBhioGdk,3647
6
- mini_code_cli-0.0.8.0.dist-info/licenses/LICENSE,sha256=WCkMINUFi--UtEg_alEE2FBKPtNKkzi7dGzaxzchifc,1088
7
- mini_code_cli-0.0.8.0.dist-info/METADATA,sha256=njKXtDgl-aLKarbmgJ8TxHWFh6Olj3vY8dBCCmxUy4w,3146
8
- mini_code_cli-0.0.8.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
9
- mini_code_cli-0.0.8.0.dist-info/entry_points.txt,sha256=MwDp5UbXpI7wodSmOSYL8_nKutnBiyp96ZDDtaX7x14,60
10
- mini_code_cli-0.0.8.0.dist-info/top_level.txt,sha256=3Hb6PpNkJYXvzDKXuuFJRvzCdsTRh0TuRWz-ScJ6qV0,14
11
- mini_code_cli-0.0.8.0.dist-info/RECORD,,