PikoAi 0.1.8__py3-none-any.whl → 0.1.10__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.
@@ -6,15 +6,17 @@ import sys
6
6
  import time
7
7
  sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../')))
8
8
  from Utils.ter_interface import TerminalInterface
9
- from Utils.executor_utils import parse_tool_call, parse_code, parse_shell_command
9
+ from Utils.executor_utils import parse_tool_call
10
10
  from Agents.Executor.prompts import get_system_prompt, get_task_prompt # Import prompts
11
11
 
12
12
  from typing import Optional
13
13
  from mistralai.models.sdkerror import SDKError # This might be an issue if LiteLLM doesn't use SDKError
14
14
  # LiteLLM maps exceptions to OpenAI exceptions.
15
15
  # We'll keep it for now and see if errors arise during testing.
16
- from Env import python_executor
17
- from Env.shell import ShellExecutor # Import ShellExecutor
16
+ # from Env import python_executor # Will be replaced by BaseEnv
17
+ # from Env.shell import ShellExecutor # Will be replaced by BaseEnv
18
+ from Env.base_env import create_environment, BaseEnv # Added
19
+ from Env import python_executor # Keep for type hint in the old execute method if needed, or remove if execute is fully removed
18
20
  from llm_interface.llm import LiteLLMInterface # Import LiteLLMInterface
19
21
 
20
22
  from Tools import tool_manager
@@ -38,8 +40,8 @@ class executor:
38
40
  self.max_iter = max_iter
39
41
  self.rate_limiter = RateLimiter(wait_time=5.0, max_retries=3)
40
42
  self.executor_prompt_init() # Update system_prompt
41
- self.python_executor = python_executor.PythonExecutor() # Initialize PythonExecutor
42
- self.shell_executor = ShellExecutor() # Initialize ShellExecutor
43
+ # self.python_executor = python_executor.PythonExecutor() # Initialize PythonExecutor
44
+ # self.shell_executor = ShellExecutor() # Initialize ShellExecutor
43
45
  self.message = [
44
46
  {"role": "system", "content": self.system_prompt},
45
47
  {"role": "user", "content": self.task_prompt}
@@ -122,89 +124,29 @@ class executor:
122
124
  # Check for tool calls
123
125
  response = self.run_inference()
124
126
  tool_call = parse_tool_call(response)
127
+
125
128
  if tool_call:
126
- print(f"\nCalling tool: {tool_call['tool_name']}")
129
+ tool_name = tool_call['tool_name']
130
+ tool_input = tool_call['input']
131
+ print(f"\nIdentified tool call: {tool_name} with input {tool_input}")
132
+
133
+ # Call the tool and append the result (no confirmation or special logic)
127
134
  try:
128
- # Pass tool name and input as separate arguments
129
- tool_output = tool_manager.call_tool(tool_call["tool_name"], tool_call["input"])
130
- self.terminal.tool_output_log(tool_output, tool_call["tool_name"])
131
- self.message.append({"role": "user", "content": f"Tool Output: {tool_output}"})
135
+ tool_output_result = tool_manager.call_tool(tool_name, tool_input)
136
+ self.terminal.tool_output_log(tool_output_result, tool_name)
137
+ print(tool_output_result)
138
+ self.message.append({"role": "user", "content": tool_output_result})
132
139
  except ValueError as e:
133
140
  error_msg = str(e)
141
+ print(f"Tool Error: {error_msg}")
134
142
  self.message.append({"role": "user", "content": f"Tool Error: {error_msg}"})
135
-
136
- else: # Not a tool call, check for code or shell command
137
- code = parse_code(response)
138
- shell_command = parse_shell_command(response)
139
-
140
- if code:
141
- # Ask user for confirmation before executing the code
142
- user_confirmation = input("Do you want to execute the Python code?")
143
- if user_confirmation.lower() == 'y':
144
- exec_result = self.python_executor.execute(code)
145
- if exec_result['output'] == "" and not exec_result['success']:
146
- error_msg = (
147
- f"Python execution failed.\n"
148
- f"Error: {exec_result.get('error', 'Unknown error')}"
149
- )
150
- print(f"there was an error in the python code execution {exec_result.get('error', 'Unknown error')}")
151
- self.message.append({"role": "user", "content": error_msg})
152
-
153
- elif exec_result['output'] == "":
154
- no_output_msg = (
155
- "Python execution completed but no output was shown. "
156
- "Please add print statements to show the results. This isn't a jupyter notebook environment. "
157
- "For example: print(your_variable) or print('Your message')"
158
- )
159
- self.message.append({"role": "user", "content": no_output_msg})
160
-
161
- #if there is an output (partial or full exeuction)
162
- else:
163
- # First, show the program output
164
- if exec_result['output'].strip():
165
- print(f"Program Output:\n{exec_result['output']}")
166
-
167
- # Then handle success/failure cases
168
- if exec_result['success']:
169
- self.message.append({"role": "user", "content": f"Program Output:\n{exec_result['output']}"})
170
- else:
171
- self.message.append({"role": "user", "content": f"Program Output:\n{exec_result['output']}\n{exec_result.get('error', 'Unknown error')}"})
172
-
173
- else:
174
- self.message.append({"role":"user","content":"User chose not to execute the Python code."})
175
- print("Python code execution skipped by the user.")
176
-
177
- elif shell_command:
178
- user_confirmation = input(f"Do you want to execute the shell command: '{shell_command}'?\n ")
179
- if user_confirmation.lower() == 'y':
180
- shell_result = self.shell_executor.execute(shell_command)
181
- if shell_result['output'] == "" and not shell_result['success']:
182
- error_msg = (
183
- f"Shell command execution failed.\n"
184
- f"Error: {shell_result.get('error', 'Unknown error')}"
185
- )
186
- print(f"there was an error in the shell command execution {shell_result.get('error', 'Unknown error')}")
187
- self.message.append({"role": "user", "content": error_msg})
188
-
189
- elif shell_result['output'] == "":
190
- print("command executed")
191
- self.message.append({"role": "user", "content": "command executed"})
192
-
193
- #if there is an output (partial or full execution)
194
- else:
195
- # First, show the command output
196
- if shell_result['output'].strip():
197
- print(f"Command Output:\n{shell_result['output']}")
198
-
199
- # Then handle success/failure cases
200
- if shell_result['success']:
201
- self.message.append({"role": "user", "content": f"Command Output:\n{shell_result['output']}"})
202
- else:
203
- self.message.append({"role": "user", "content": f"Command Output:\n{shell_result['output']}\n{shell_result.get('error', 'Unknown error')}"})
204
-
205
- else:
206
- self.message.append({"role":"user","content":"User chose not to execute the shell command."})
207
- print("Shell command execution skipped by the user.")
143
+
144
+ else: # Not a tool call, could be a direct response or requires clarification
145
+ # This part handles responses that are not formatted as tool calls.
146
+ # It might be a final answer, a question, or just conversational text.
147
+ # The existing logic for TASK_DONE or asking for next step handles this.
148
+ # No specific code/shell parsing here anymore as they are tools.
149
+ pass # Explicitly pass if no tool call and no old code/shell logic.
208
150
 
209
151
  # Check if task is done
210
152
  if "TASK_DONE" in response:
@@ -218,24 +160,26 @@ class executor:
218
160
  if not task_done:
219
161
  print(f"Task could not be completed within {self.max_iter} iterations.")
220
162
 
221
- def execute(self, code: str, exec_env: python_executor.PythonExecutor):
222
- """Executes the given Python code using the provided execution environment."""
223
- result = exec_env.execute(code)
224
- return result
163
+ # This method is superseded by the BaseEnv approach in run_task
164
+ # def execute(self, code: str, exec_env: python_executor.PythonExecutor):
165
+ # """Executes the given Python code using the provided execution environment."""
166
+ # result = exec_env.execute(code)
167
+ # return result
225
168
 
226
169
  if __name__ == "__main__":
227
- e1 = executor("")
228
- user_prompt = input("Please enter your prompt: ")
229
- e1.user_prompt = user_prompt
230
- e1.executor_prompt_init() # Update system_prompt
231
- e1.message = [
232
- {"role": "system", "content": e1.system_prompt},
233
- {"role": "user", "content": e1.task_prompt}
234
- ] # Reset message list properly
235
- e1.run()
236
-
237
- while True:
238
- user_prompt = input("Please enter your prompt: ")
239
- e1.message.append({"role": "user", "content": user_prompt})
240
- # e1.message.append({"role":"user","content":e1.system_prompt})
241
- e1.run()
170
+ # e1 = executor("") # Commenting out example usage for now as it might need adjustment
171
+ # user_prompt = input("Please enter your prompt: ")
172
+ # e1.user_prompt = user_prompt
173
+ # e1.executor_prompt_init() # Update system_prompt
174
+ # e1.message = [
175
+ # {"role": "system", "content": e1.system_prompt},
176
+ # {"role": "user", "content": e1.task_prompt}
177
+ # ] # Reset message list properly
178
+ # e1.run()
179
+
180
+ # while True:
181
+ # user_prompt = input("Please enter your prompt: ")
182
+ # e1.message.append({"role": "user", "content": user_prompt})
183
+ # # e1.message.append({"role":"user","content":e1.system_prompt})
184
+ # e1.run()
185
+ pass # Placeholder if main execution is commented out
@@ -17,9 +17,7 @@ You have access to the following tools:
17
17
 
18
18
  Your primary objective is to accomplish the user's goal by performing step-by-step actions. These actions can include:
19
19
  1. Calling a tool
20
- 2. Executing Python code
21
- 3. Executing Shell commands
22
- 4. Providing a direct response
20
+ 2. Providing a direct response
23
21
 
24
22
  You must break down the user's goal into smaller steps and perform one action at a time. After each action, carefully evaluate the output to determine the next step.
25
23
 
@@ -33,14 +31,9 @@ You must break down the user's goal into smaller steps and perform one action at
33
31
  }}
34
32
  }}
35
33
  <<END_TOOL_CALL>>
36
- - **Code Execution**: Write Python code when no tool is suitable or when custom logic is needed. Format:
37
- <<CODE>>
38
- your_python_code_here
39
- <<CODE>>
40
- - **Shell Command Execution**: Execute shell commands when needed. Format:
41
- <<SHELL_COMMAND>>
42
- your_shell_command_here
43
- <<END_SHELL_COMMAND>>
34
+ This includes executing Python code and shell commands:
35
+ `execute_python_code`: {{"code": "your_python_code_here"}}
36
+ `execute_shell_command`: {{"command": "your_shell_command_here"}}
44
37
  - **Direct Response**: Provide a direct answer if the task doesn't require tools or code.
45
38
 
46
39
  ### Important Notes:
@@ -68,16 +61,7 @@ Following are the things that you must read carefully and remember:
68
61
  }
69
62
  }
70
63
  <<END_TOOL_CALL>>
71
-
72
- - For code execution, use:
73
- <<CODE>>
74
- your_python_code_here
75
- <<CODE>>
76
-
77
- - For shell command execution, use:
78
- <<SHELL_COMMAND>>
79
- your_shell_command_here
80
- <<END_SHELL_COMMAND>>
64
+ Remember that executing Python code and shell commands is now done through specific tool calls (`execute_python_code` and `execute_shell_command`).
81
65
 
82
66
  After each action, always evaluate the output to decide your next step. Only include 'TASK_DONE'
83
67
  When the entire task is completed. Do not end the task immediately after a tool call or code execution without
Env/base_env.py CHANGED
@@ -1,25 +1,30 @@
1
- from Env.js_executor import JavaScriptExecutor #class import
2
- from Env.python_executor import PythonExecutor #class import
3
-
4
1
  # to perform funciton ask whether to execute code
5
2
 
6
3
 
7
4
  class BaseEnv:
8
5
 
9
6
 
10
- def __init__(self, language,code):
11
- self.language = language
7
+ def __init__(self):
8
+ pass
9
+
12
10
 
11
+ def execute(self, code_or_command: str):
12
+ raise NotImplementedError("This method should be overridden by subclasses")
13
13
 
14
- def execute(self):
14
+ def stop_execution(self):
15
15
  raise NotImplementedError("This method should be overridden by subclasses")
16
16
 
17
17
 
18
18
  def create_environment(language):
19
+ # Moved imports inside the function to avoid circular dependencies during testing
20
+ from Env.js_executor import JavaScriptExecutor #class import
21
+ from Env.python_executor import PythonExecutor #class import
22
+ from Env.shell import ShellExecutor #class import
23
+
19
24
  if language == "python":
20
25
  return PythonExecutor()
21
- elif language == "javascript":
22
- return JavaScriptExecutor()
26
+ elif language == "shell":
27
+ return ShellExecutor()
23
28
  else:
24
29
  raise ValueError(f"Unsupported language: {language}")
25
30
 
Env/python_executor.py CHANGED
@@ -21,9 +21,13 @@ import os
21
21
  from typing import Dict
22
22
  import textwrap
23
23
  import sys
24
+ from Src.Env.base_env import BaseEnv
25
+ import time
24
26
 
25
- class PythonExecutor:
27
+ class PythonExecutor(BaseEnv):
26
28
  def __init__(self):
29
+ super().__init__()
30
+ self.process = None
27
31
  self.forbidden_terms = [
28
32
  'import os', 'import sys', 'import subprocess',
29
33
  'open(', 'exec(', 'eval(',
@@ -34,52 +38,80 @@ class PythonExecutor:
34
38
  code_lower = code.lower()
35
39
  return not any(term.lower() in code_lower for term in self.forbidden_terms)
36
40
 
37
- def execute(self, code: str) -> Dict[str, str]:
41
+ def execute(self, code_or_command: str) -> Dict[str, str]:
38
42
  """Executes Python code in a separate process and returns the result"""
39
43
 
40
44
  # Basic safety check
41
- if not self.basic_code_check(code):
45
+ if not self.basic_code_check(code_or_command):
42
46
  return {
43
47
  'success': False,
44
48
  'output': 'Error: Code contains potentially unsafe operations. You can try and use tools to achieve same functionality.',
45
49
  'error': 'Security check failed'
46
50
  }
47
51
 
48
- # Create a temporary file to store the code
49
- with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
50
- # Properly indent the code to fit inside the try block
51
- indented_code = textwrap.indent(code, ' ')
52
- # Wrap the indented code to capture output
53
- wrapped_code = f"""
52
+ # Properly indent the code to fit inside the try block
53
+ indented_code = textwrap.indent(code_or_command, ' ')
54
+ # Wrap the indented code to capture output
55
+ wrapped_code = f"""
54
56
  try:
55
57
  {indented_code}
56
58
  except Exception as e:
57
59
  print(f"Error: {{str(e)}}")
58
60
  """
59
- f.write(wrapped_code)
60
- temp_file = f.name
61
+
61
62
 
62
63
  try:
63
64
  # Execute the code in a subprocess
64
- result = subprocess.run(
65
- [sys.executable, temp_file],
66
- capture_output=True,
65
+ self.process = subprocess.Popen(
66
+ [sys.executable, "-u", "-c", wrapped_code],
67
+ stdout=subprocess.PIPE,
68
+ stderr=subprocess.PIPE,
67
69
  text=True,
68
- timeout=30 # 30 second timeout
70
+ bufsize=1, # Line buffered
71
+ universal_newlines=True
69
72
  )
70
-
73
+
74
+ stdout_data = []
75
+ stderr_data = []
76
+ start_time = time.time()
77
+
78
+ # First read all stdout
79
+ for line in self.process.stdout:
80
+ # Check for timeout
81
+ if time.time() - start_time > 30:
82
+ self.process.kill()
83
+ return {
84
+ 'success': False,
85
+ 'output': 'Execution timed out after 30 seconds',
86
+ 'error': 'Timeout error'
87
+ }
88
+
89
+ stdout_data.append(line)
90
+ print(line, end='', flush=True) # Print in real-time
91
+
92
+ # Then read all stderr
93
+ for line in self.process.stderr:
94
+ # Check for timeout
95
+ if time.time() - start_time > 30:
96
+ self.process.kill()
97
+ return {
98
+ 'success': False,
99
+ 'output': 'Execution timed out after 30 seconds',
100
+ 'error': 'Timeout error'
101
+ }
102
+
103
+ stderr_data.append(line)
104
+ print(line, end='', file=sys.stderr, flush=True) # Print in real-time
105
+
106
+ # Wait for process to complete
107
+ returncode = self.process.wait()
108
+
71
109
  return {
72
- 'success': result.returncode == 0,
73
- 'output': result.stdout if result.returncode == 0 else result.stderr,
74
- 'error': result.stderr if result.returncode != 0 else ''
110
+ 'success': returncode == 0,
111
+ 'output': ''.join(stdout_data) if returncode == 0 else ''.join(stderr_data),
112
+ 'error': ''.join(stderr_data) if returncode != 0 else ''
75
113
  }
76
114
 
77
- except subprocess.TimeoutExpired:
78
- return {
79
- 'success': False,
80
- 'output': 'Execution timed out after 30 seconds',
81
- 'error': 'Timeout error'
82
- }
83
115
  except Exception as e:
84
116
  return {
85
117
  'success': False,
@@ -87,10 +119,18 @@ except Exception as e:
87
119
  'error': str(e)
88
120
  }
89
121
  finally:
90
- # Clean up the temporary file
122
+ self.process = None # Reset process
123
+
124
+
125
+ def stop_execution(self):
126
+ if self.process and hasattr(self.process, 'pid') and self.process.pid is not None:
91
127
  try:
92
- os.unlink(temp_file)
93
- except:
94
- pass # Ignore cleanup errors
95
-
128
+ self.process.terminate()
129
+ print(f"Attempted to terminate Python process with PID: {self.process.pid}")
130
+ except Exception as e:
131
+ print(f"Error terminating Python process with PID {self.process.pid}: {e}")
132
+ finally:
133
+ self.process = None
134
+ else:
135
+ print("No active Python process to stop.")
96
136
 
Env/shell.py CHANGED
@@ -1,12 +1,21 @@
1
1
  import subprocess
2
+ import time
3
+ import sys
4
+ from Src.Env.base_env import BaseEnv
5
+ import re
2
6
 
3
- class ShellExecutor:
4
- def execute(self, command: str) -> dict:
7
+ class ShellExecutor(BaseEnv):
8
+ def __init__(self):
9
+ super().__init__()
10
+ self.process = None
11
+
12
+ def execute(self, code_or_command: str) -> dict:
5
13
  """
6
- Executes a shell command and captures its output, error, and success status.
14
+ Executes a shell command and streams its output in real-time.
15
+ Strictly prevents execution of harmful commands and access to sensitive directories.
7
16
 
8
17
  Args:
9
- command: The shell command to execute.
18
+ code_or_command: The shell command to execute.
10
19
 
11
20
  Returns:
12
21
  A dictionary with the following keys:
@@ -15,24 +24,142 @@ class ShellExecutor:
15
24
  - 'success': A boolean indicating whether the command executed successfully.
16
25
  """
17
26
  try:
18
- process = subprocess.run(
19
- command,
27
+ # Strict security check for harmful commands and sensitive directories
28
+ forbidden_patterns = [
29
+ r'rm\s+-rf\s+/?(\s|$)',
30
+ r'rm\s+-rf\s+--no-preserve-root',
31
+ r'rm\s+-rf\s+/\\?',
32
+ r'shutdown(\s|$)',
33
+ r'reboot(\s|$)',
34
+ r'halt(\s|$)',
35
+ r':(){:|:&};:', # fork bomb
36
+ r'chmod\s+777\s+/(\s|$)',
37
+ r'chown\s+root',
38
+ r'\bmkfs\b',
39
+ r'\bdd\b.*\bif=\/dev\/zero\b',
40
+ r'\bdd\b.*\bof=\/dev\/sda',
41
+ r'\bpoweroff\b',
42
+ r'\binit\s+0\b',
43
+ r'\bsudo\s+rm\s+-rf\s+/?',
44
+ r'\bsudo\s+shutdown',
45
+ r'\bsudo\s+reboot',
46
+ r'\bsudo\s+halt',
47
+ r'\bsudo\s+mkfs',
48
+ r'\bsudo\s+dd',
49
+ r'\bsudo\s+init\s+0',
50
+ r'\bsudo\s+poweroff',
51
+ r'\bsudo\s+chmod\s+777\s+/',
52
+ r'\bsudo\s+chown\s+root',
53
+ r'\bdel\b.*\/s.*\/q.*\/f.*C:\\', # Windows
54
+ r'format\s+C:',
55
+ r'rd\s+/?s\s+/?q\s+C:\\',
56
+ r'\bshutdown\b.*\/s',
57
+ r'\bshutdown\b.*\/r',
58
+ r'\bshutdown\b.*\/f',
59
+ r'\bshutdown\b.*\/p',
60
+ r'\bshutdown\b.*\/t',
61
+ r'\bshutdown\b.*\/a',
62
+ r'\bnet\s+user\s+.*\s+/delete',
63
+ r'\bnet\s+user\s+administrator\s+/active:no',
64
+ r'\bnet\s+user\s+administrator\s+/active:yes',
65
+ r'\bnet\s+localgroup\s+administrators\s+.*\s+/delete',
66
+ r'\bnet\s+localgroup\s+administrators\s+.*\s+/add',
67
+ ]
68
+ sensitive_dirs = [
69
+ '/', '/etc', '/bin', '/usr', '/var', '/root', '/boot', '/dev', '/proc', '/sys', '/lib', '/lib64',
70
+ 'C:\\', 'C:/', 'C:\\Windows', 'C:/Windows', 'C:\\System32', 'C:/System32',
71
+ 'D:\\', 'D:/', 'E:\\', 'E:/'
72
+ ]
73
+ # Check for forbidden patterns
74
+ for pattern in forbidden_patterns:
75
+ if re.search(pattern, code_or_command, re.IGNORECASE):
76
+ return {
77
+ 'success': False,
78
+ 'output': 'Blocked potentially harmful command.',
79
+ 'error': f'Command matches forbidden pattern: {pattern}'
80
+ }
81
+ # Check for sensitive directory access
82
+ for sensitive_dir in sensitive_dirs:
83
+ # Only block if the command is trying to directly access or operate on the sensitive dir
84
+ if re.search(rf'\b{sensitive_dir}\b', code_or_command, re.IGNORECASE):
85
+ return {
86
+ 'success': False,
87
+ 'output': f'Blocked access to sensitive directory: {sensitive_dir}',
88
+ 'error': f'Attempted access to sensitive directory: {sensitive_dir}'
89
+ }
90
+
91
+ # Execute the command in a subprocess
92
+ self.process = subprocess.Popen(
93
+ code_or_command,
20
94
  shell=True,
21
- capture_output=True,
95
+ stdout=subprocess.PIPE,
96
+ stderr=subprocess.PIPE,
22
97
  text=True,
23
- check=False # Don't raise an exception on non-zero exit codes
98
+ bufsize=1, # Line buffered
99
+ universal_newlines=True
24
100
  )
101
+
102
+ stdout_data = []
103
+ stderr_data = []
104
+ start_time = time.time()
105
+
106
+ # First read all stdout
107
+ for line in self.process.stdout:
108
+ # Check for timeout
109
+ if time.time() - start_time > 30:
110
+ self.process.kill()
111
+ return {
112
+ 'success': False,
113
+ 'output': 'Execution timed out after 30 seconds',
114
+ 'error': 'Timeout error'
115
+ }
116
+
117
+ stdout_data.append(line)
118
+ print(line, end='', flush=True) # Print in real-time
119
+
120
+ # Then read all stderr
121
+ for line in self.process.stderr:
122
+ # Check for timeout
123
+ if time.time() - start_time > 30:
124
+ self.process.kill()
125
+ return {
126
+ 'success': False,
127
+ 'output': 'Execution timed out after 30 seconds',
128
+ 'error': 'Timeout error'
129
+ }
130
+
131
+ stderr_data.append(line)
132
+ print(line, end='', file=sys.stderr, flush=True) # Print in real-time
133
+
134
+ # Wait for process to complete
135
+ returncode = self.process.wait()
136
+
25
137
  return {
26
- "output": process.stdout,
27
- "error": process.stderr,
28
- "success": process.returncode == 0,
138
+ 'success': returncode == 0,
139
+ 'output': ''.join(stdout_data) if returncode == 0 else ''.join(stderr_data),
140
+ 'error': ''.join(stderr_data) if returncode != 0 else ''
29
141
  }
142
+
30
143
  except Exception as e:
31
144
  return {
32
- "output": "",
33
- "error": str(e),
34
- "success": False,
145
+ 'success': False,
146
+ 'output': f'Error: {str(e)}',
147
+ 'error': str(e)
35
148
  }
149
+ finally:
150
+ self.process = None # Reset process
151
+
152
+ def stop_execution(self):
153
+ if self.process and hasattr(self.process, 'pid') and self.process.pid is not None:
154
+ try:
155
+ self.process.terminate()
156
+ print(f"Attempted to terminate shell process with PID: {self.process.pid}")
157
+ except Exception as e:
158
+ print(f"Error terminating shell process with PID {self.process.pid}: {e}")
159
+ finally:
160
+ self.process = None
161
+ else:
162
+ print("No active shell process to stop.")
36
163
 
37
164
  if __name__ == '__main__':
38
165
  # Example usage (optional, for testing)
Env/tests/__init__.py ADDED
File without changes
@@ -0,0 +1,70 @@
1
+ import unittest
2
+ from Src.Env.python_executor import PythonExecutor
3
+ import time
4
+ import threading
5
+
6
+ class TestPythonExecutor(unittest.TestCase):
7
+
8
+ def test_execute_simple_script(self):
9
+ executor = PythonExecutor()
10
+ result = executor.execute("print('hello python')")
11
+ self.assertTrue(result["success"])
12
+ self.assertEqual(result["output"].strip(), "hello python")
13
+ self.assertEqual(result["error"], "")
14
+
15
+ def test_execute_script_with_error(self):
16
+ executor = PythonExecutor()
17
+ result = executor.execute("1/0")
18
+ self.assertFalse(result["success"])
19
+ self.assertTrue("ZeroDivisionError" in result["output"]) # Error message goes to stdout for python_executor
20
+ self.assertEqual(result["error"], "") # stderr should be empty as Popen merges stderr to stdout in this case
21
+
22
+ def test_stop_execution_long_script(self):
23
+ executor = PythonExecutor()
24
+ long_script = "import time; time.sleep(5); print('should not print')"
25
+
26
+ execute_result = {}
27
+ def target():
28
+ res = executor.execute(long_script)
29
+ execute_result.update(res)
30
+
31
+ thread = threading.Thread(target=target)
32
+ thread.start()
33
+
34
+ time.sleep(1) # Give the script time to start
35
+ executor.stop_execution()
36
+
37
+ thread.join(timeout=2) # Wait for the thread to finish (should be quick after stop)
38
+
39
+ self.assertFalse(execute_result.get("success", True), "Script execution should have failed or been stopped.")
40
+ # Depending on timing, the process might be killed before it produces output,
41
+ # or it might produce a timeout error, or a specific error from being terminated.
42
+ # We check if the output indicates it didn't complete normally.
43
+ output = execute_result.get("output", "")
44
+ error = execute_result.get("error", "")
45
+
46
+ # Check if 'should not print' is NOT in the output
47
+ self.assertNotIn("should not print", output, "Script should have been terminated before completion.")
48
+
49
+ # Check for signs of termination or timeout
50
+ # This part is a bit tricky as the exact message can vary.
51
+ # If basic_code_check fails, output is "Error: Code contains potentially unsafe operations..."
52
+ # If timeout in communicate(), output is "Execution timed out..."
53
+ # If process is terminated, output might be empty or contain partial error.
54
+ # For now, we'll accept that if "should not print" is not there, it's a good sign.
55
+ # A more robust check might involve looking for specific error messages related to termination if available.
56
+
57
+ # A simple check that process is no longer listed in executor
58
+ self.assertIsNone(executor.process, "Executor process should be None after stopping.")
59
+
60
+
61
+ def test_stop_execution_no_script(self):
62
+ executor = PythonExecutor()
63
+ try:
64
+ executor.stop_execution()
65
+ except Exception as e:
66
+ self.fail(f"stop_execution with no script raised an exception: {e}")
67
+ self.assertIsNone(executor.process, "Executor process should be None.")
68
+
69
+ if __name__ == '__main__':
70
+ unittest.main()
@@ -0,0 +1,29 @@
1
+ import unittest
2
+ from Src.Env.shell import ShellExecutor
3
+
4
+ class TestShellExecutor(unittest.TestCase):
5
+
6
+ def test_execute_success(self):
7
+ executor = ShellExecutor()
8
+ result = executor.execute("echo hello")
9
+ self.assertTrue(result["success"])
10
+ self.assertEqual(result["output"].strip(), "hello")
11
+ self.assertEqual(result["error"], "")
12
+
13
+ def test_execute_error(self):
14
+ executor = ShellExecutor()
15
+ result = executor.execute("ls non_existent_directory_for_test")
16
+ self.assertFalse(result["success"])
17
+ self.assertEqual(result["output"], "")
18
+ self.assertTrue("non_existent_directory_for_test" in result["error"])
19
+
20
+ def test_stop_execution(self):
21
+ executor = ShellExecutor()
22
+ # This test mainly ensures that calling stop_execution doesn't raise an error
23
+ try:
24
+ executor.stop_execution()
25
+ except Exception as e:
26
+ self.fail(f"stop_execution raised an exception: {e}")
27
+
28
+ if __name__ == '__main__':
29
+ unittest.main()
OpenCopilot.py CHANGED
@@ -6,9 +6,40 @@ from prompt_toolkit import PromptSession, HTML
6
6
  from prompt_toolkit.completion import Completer, Completion
7
7
  from prompt_toolkit.shortcuts import print_formatted_text
8
8
  from prompt_toolkit.formatted_text import FormattedText
9
+ from prompt_toolkit.key_binding import KeyBindings
10
+ from prompt_toolkit.application.current import get_app
9
11
 
12
+ from Tools.file_task import file_reader
10
13
  from Agents.Executor.executor import executor
11
14
 
15
+ # Custom key bindings for handling Enter key
16
+ kb = KeyBindings()
17
+
18
+ @kb.add('enter')
19
+ def handle_enter(event):
20
+ """Handle Enter key press based on completion state"""
21
+ buff = event.current_buffer
22
+ app = get_app()
23
+
24
+ # If completion menu is open, select the current completion
25
+ if app.current_buffer.complete_state:
26
+ # Get the current completion
27
+ current_completion = app.current_buffer.complete_state.current_completion
28
+ if current_completion:
29
+ # Get the text before cursor
30
+ text_before_cursor = buff.text[:buff.cursor_position]
31
+ # Find the last @ symbol
32
+ last_at_pos = text_before_cursor.rindex('@')
33
+ # Delete text from @ to cursor position
34
+ buff.delete_before_cursor(count=buff.cursor_position - last_at_pos - 1)
35
+ # Insert the completion
36
+ buff.insert_text(current_completion.text)
37
+ # Close the completion menu
38
+ buff.complete_state = None
39
+ else:
40
+ # If no completion menu, submit the command
41
+ buff.validate_and_handle()
42
+
12
43
  class FilePathCompleter(Completer):
13
44
  def get_completions(self, document, complete_event):
14
45
  text_before_cursor = document.text_before_cursor
@@ -74,7 +105,12 @@ class FilePathCompleter(Completer):
74
105
  class OpenCopilot:
75
106
  def __init__(self):
76
107
  self.e1 = None # Initialize as None, will be set in run()
77
- self.session = PromptSession(completer=FilePathCompleter())
108
+ # Initialize session with custom key bindings
109
+ self.session = PromptSession(
110
+ completer=FilePathCompleter(),
111
+ key_bindings=kb, # Add custom key bindings
112
+ complete_while_typing=True # Enable completion while typing
113
+ )
78
114
 
79
115
  def extract_files_and_process_prompt(self, user_input):
80
116
  """Extract file paths from @ commands and process the prompt."""
@@ -93,10 +129,11 @@ class OpenCopilot:
93
129
 
94
130
  if os.path.exists(expanded_path):
95
131
  if os.path.isfile(expanded_path):
96
- try:
97
- with open(expanded_path, 'r', encoding='utf-8') as f:
98
- content = f.read()
99
-
132
+ # Call file_reader to get file content
133
+ file_read_result = file_reader(file_path=expanded_path)
134
+
135
+ if file_read_result["success"]:
136
+ content = file_read_result["output"]
100
137
  # Add file content with clear formatting
101
138
  file_contents.append(f"=== Content of file: {expanded_path} ===\n{content}\n=== End of file: {expanded_path} ===\n")
102
139
  # Remove the @file pattern from the processed prompt
@@ -105,10 +142,10 @@ class OpenCopilot:
105
142
  print_formatted_text(FormattedText([
106
143
  ('class:success', f"✓ Loaded file: {expanded_path}")
107
144
  ]))
108
-
109
- except Exception as e:
145
+ else:
146
+ error_message = file_read_result["output"]
110
147
  print_formatted_text(FormattedText([
111
- ('class:error', f"✗ Error reading file {expanded_path}: {str(e)}")
148
+ ('class:error', f"✗ Error reading file {expanded_path}: {error_message}")
112
149
  ]))
113
150
  else:
114
151
  # For directories, just append the path to the processed prompt
Tools/tool_dir.json CHANGED
@@ -70,5 +70,21 @@
70
70
  "prompt": "The message to display to the user",
71
71
  "input_type": "Type of input to validate (text, number, boolean, optional, defaults to text)"
72
72
  }
73
+ },
74
+ {
75
+ "name": "execute_python_code",
76
+ "summary": "Executes a given snippet of Python code. The code should be self-contained. Input should be a JSON object with a 'code' key containing the Python code string.",
77
+ "arguments": {
78
+ "code": "The Python code to execute."
79
+ },
80
+ "input_schema": {"type": "object", "properties": {"code": {"type": "string"}}, "required": ["code"]}
81
+ },
82
+ {
83
+ "name": "execute_shell_command",
84
+ "summary": "Executes a given shell command. Input should be a JSON object with a 'command' key containing the shell command string.",
85
+ "arguments": {
86
+ "command": "The shell command to execute."
87
+ },
88
+ "input_schema": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]}
73
89
  }
74
90
  ]
Tools/tool_manager.py CHANGED
@@ -7,26 +7,64 @@ from Tools.web_search import web_search
7
7
  from Tools.file_task import file_reader, file_maker, file_writer, directory_maker
8
8
  from Tools.system_details import get_os_details, get_datetime, get_memory_usage, get_cpu_info
9
9
  from Tools.userinp import get_user_input
10
+ from Src.Env.python_executor import PythonExecutor
11
+ from Src.Env.shell import ShellExecutor
10
12
 
11
13
  #need to transform it into map of dictionary
12
14
  #name : [function : xyz,description : blah bah]
13
15
 
14
- tools_function_map = {
15
- "web_loader": load_data,
16
- "web_search": web_search,
17
- "file_maker": file_maker,
18
- "file_reader":file_reader,
19
- "directory_maker":directory_maker,
20
- "file_writer":file_writer,
21
- "get_os_details": get_os_details,
22
- "get_datetime": get_datetime,
23
- "get_memory_usage": get_memory_usage,
24
- "get_cpu_info": get_cpu_info,
25
- "get_user_input": get_user_input
26
- }
27
16
 
28
17
 
29
18
 
19
+ def execute_python_code_tool(code: str) -> str:
20
+ """
21
+ Prompts for confirmation, then executes the given Python code and returns a formatted result string.
22
+ """
23
+ user_confirmation = input(f"Do you want to execute this Python code snippet?\n```python\n{code}\n```\n(y/n): ")
24
+ if user_confirmation.lower() != 'y':
25
+ print("Python code execution skipped by the user.")
26
+ return "User chose not to execute the Python code."
27
+ executor = PythonExecutor()
28
+ result = executor.execute(code)
29
+ if result['output'] == "" and not result['success']:
30
+ error_msg = (
31
+ f"Python execution failed.\n"
32
+ f"Error: {result.get('error', 'Unknown error')}"
33
+ )
34
+ return error_msg
35
+ elif result['output'] == "":
36
+ no_output_msg = (
37
+ "Python execution completed but no output was produced. "
38
+ "Ensure your code includes print() statements to show results."
39
+ )
40
+ return no_output_msg
41
+ else:
42
+ if result['success']:
43
+ return f"Program Output:\n{result['output']}"
44
+ else:
45
+ return f"Program Output:\n{result['output']}\nError: {result.get('error', 'Unknown error')}"
46
+
47
+ def execute_shell_command_tool(command: str) -> str:
48
+ """
49
+ Prompts for confirmation, then executes the given shell command and returns a formatted result string.
50
+ """
51
+ user_confirmation = input(f"Do you want to execute the shell command: '{command}'? (y/n): ")
52
+ if user_confirmation.lower() != 'y':
53
+ print("Shell command execution skipped by the user.")
54
+ return "User chose not to execute the shell command."
55
+ executor = ShellExecutor()
56
+ result = executor.execute(command)
57
+ if result['output'] == "":
58
+ if result['success']:
59
+ return "Shell command executed successfully with no output."
60
+ else:
61
+ return f"Shell command executed with no output, but an error occurred: {result.get('error', 'Unknown error')}"
62
+ else:
63
+ if result['success']:
64
+ return f"Command Output:\n{result['output']}"
65
+ else:
66
+ return f"Command Output:\n{result['output']}\nError: {result.get('error', 'Unknown error')}"
67
+
30
68
  def call_tool(tool_name, tool_input):
31
69
  """
32
70
  Calls the appropriate tool function with the given input.
@@ -41,6 +79,21 @@ def call_tool(tool_name, tool_input):
41
79
  else:
42
80
  raise ValueError(f"Tool '{tool_name}' not found. Check the tools available in the tool directory")
43
81
 
82
+ tools_function_map = {
83
+ "web_loader": load_data,
84
+ "web_search": web_search,
85
+ "file_maker": file_maker,
86
+ "file_reader":file_reader,
87
+ "directory_maker":directory_maker,
88
+ "file_writer":file_writer,
89
+ "get_os_details": get_os_details,
90
+ "get_datetime": get_datetime,
91
+ "get_memory_usage": get_memory_usage,
92
+ "get_cpu_info": get_cpu_info,
93
+ "get_user_input": get_user_input,
94
+ "execute_python_code": execute_python_code_tool,
95
+ "execute_shell_command": execute_shell_command_tool,
96
+ }
44
97
 
45
98
  # print(call_tool("web_loader","https://www.toastmasters.org"))
46
99
  # print(call_tool("web_search","manus ai"))
Utils/executor_utils.py CHANGED
@@ -13,21 +13,3 @@ def parse_tool_call(response: str) -> Optional[dict]:
13
13
  except json.JSONDecodeError:
14
14
  return None
15
15
  return None
16
-
17
- def parse_code(response: str) -> Optional[str]:
18
- """
19
- Parses code from the response.
20
- """
21
- if "<<CODE>>" in response and "<<CODE>>" in response: # There was a typo in the original file, it checked for <<CODE>> twice
22
- code = response.split("<<CODE>>")[1].split("<<CODE>>")[0].strip()
23
- return code
24
- return None
25
-
26
- def parse_shell_command(response: str) -> Optional[str]:
27
- """
28
- Parses a shell command from the response.
29
- """
30
- if "<<SHELL_COMMAND>>" in response and "<<END_SHELL_COMMAND>>" in response:
31
- shell_command = response.split("<<SHELL_COMMAND>>")[1].split("<<END_SHELL_COMMAND>>")[0].strip()
32
- return shell_command
33
- return None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PikoAi
3
- Version: 0.1.8
3
+ Version: 0.1.10
4
4
  Summary: An AI-powered task automation tool
5
5
  Home-page: https://github.com/nihaaaar22/OS-Assistant
6
6
  Author: Nihar S
@@ -0,0 +1,35 @@
1
+ OpenCopilot.py,sha256=vBAuU6MNhYdnyIIrDWOBSlxa0iyjADJLq5eCxUhlffw,12240
2
+ cli.py,sha256=hY6KUxvKvJOFThZT--r6m2nzOEMIOVtiVptewsi9Z9w,13868
3
+ Agents/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ Agents/Executor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ Agents/Executor/executor.py,sha256=RGYh4bmG5-cvsQPrlCTGNYzQbsnZcQt7eoIpEH9g6OY,8808
6
+ Agents/Executor/prompts.py,sha256=bcVxBBCEda7JZtnA1KoMM8KayKAqFeQCP4lY6EbodzQ,3607
7
+ Env/__init__.py,sha256=KLe7UcNV5L395SxhMwbYGyu7KPrSNaoV_9QJo3mLop0,196
8
+ Env/base_env.py,sha256=K4PoWwPXn3pKeu7_-JOlUuyNbyYQ9itMhQybFOm-3K4,1563
9
+ Env/base_executor.py,sha256=awTwJ44CKWV4JO2KUHfHDX0p1Ujw55hlaL5oNYTEW9M,893
10
+ Env/env.py,sha256=I3lOoyBJR5QNq3tWk_HVH6ncRx1vnilRYmj7b7h4jic,114
11
+ Env/js_executor.py,sha256=tEAg5ho8Pa8LzxUbS1Idau8XuJWZZqPiNlvFPDwGkgc,2690
12
+ Env/python_executor.py,sha256=i1SelJ2GdQpGVjtAVQmp__0bA95OurnU4DUyu_Yfb9g,4818
13
+ Env/shell.py,sha256=7jbSMPh-1Cx6xCzvS78MQbneYfBSNlItl-EHjDR1Zw4,7294
14
+ Env/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ Env/tests/test_python_executor.py,sha256=5kfs0cOf-RgWTOers_1wB0yvYSF-HrHPsraJ-PxgR50,3156
16
+ Env/tests/test_shell_executor.py,sha256=-RcCdSUMoRAXHISIh0IR5MCutco80fX2S3sQBcinc_Q,1034
17
+ Tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
+ Tools/file_task.py,sha256=VUhWq_G-SWvGahQo8PG7TOpElHUW3BGLUabrTdJS89o,12151
19
+ Tools/system_details.py,sha256=7-mTm3CG4NoatHcvcosalEgEcpWlNsCsZ7kuS3y_EmY,2262
20
+ Tools/tool_dir.json,sha256=pEqI-1rKud-VEWHYLF2Pbv6xwQZWy62HUF_1hjcdhCY,3128
21
+ Tools/tool_manager.py,sha256=Q42jnN9eoBm98cgyEpAnINPZYKegdKO-T6zQY2Ns2jE,4129
22
+ Tools/userinp.py,sha256=vUhEj3y1W1_ZFHqo2xQwvqDyeOg3VsisSKTI0EurUH8,1205
23
+ Tools/web_loader.py,sha256=PyZk2g7WngZT0tCLs9Danx20dYspnaZwy4rlVE9Sx_4,5054
24
+ Tools/web_search.py,sha256=4EGq1VZqfDgG-_yXTd4_Ha1iEUcR-szdlgRV7oFPru4,1259
25
+ Utils/__init__.py,sha256=oukU0ufroPRd8_N8d2xiFes9CTxSaw4NA6p2nS1kkSg,16
26
+ Utils/executor_utils.py,sha256=WwK3TKgw_hG_crg7ijRaqfidYnnNXYbbs37vKZRYK-0,491
27
+ Utils/ter_interface.py,sha256=Zay9mwyAyKYTNQAKOWXHAa3upo9TWprSf26STiHXk0g,6255
28
+ llm_interface/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
+ llm_interface/llm.py,sha256=tI_KDOW14QLWowA7bB3GPe2qjlk0sjS5fBavs9XD1fo,5185
30
+ pikoai-0.1.10.dist-info/licenses/LICENSE,sha256=cELUVOboOAderKFp8bdtcM5VyJi61YH1oDbRhOuoQZw,1067
31
+ pikoai-0.1.10.dist-info/METADATA,sha256=51na_vrLeUXkpQiGmpZCxHsJVzrkmlzCBt_Fjq0nHOw,2962
32
+ pikoai-0.1.10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
33
+ pikoai-0.1.10.dist-info/entry_points.txt,sha256=xjZnheDymNDnQ0o84R0jZKEITrhNbzQWN-AhqfA_d6s,50
34
+ pikoai-0.1.10.dist-info/top_level.txt,sha256=hWzBNE7UQsuNcENIOksGcJED08k3ZGRRn2X5jnStICU,53
35
+ pikoai-0.1.10.dist-info/RECORD,,
@@ -1,32 +0,0 @@
1
- OpenCopilot.py,sha256=6210OONj0yV-LPzkCdZ1RUiHC5NZvLsD-y93Y1zurX0,10687
2
- cli.py,sha256=hY6KUxvKvJOFThZT--r6m2nzOEMIOVtiVptewsi9Z9w,13868
3
- Agents/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- Agents/Executor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- Agents/Executor/executor.py,sha256=zaFO4IQNE1PscvGPV8zoRsQAJAYVTz2JsD_zUBLyYwg,12360
6
- Agents/Executor/prompts.py,sha256=wLS3lPAYWjeKF02LzJ8vP5bZ2VQrMJUd4A7rBfl6qSQ,3846
7
- Env/__init__.py,sha256=KLe7UcNV5L395SxhMwbYGyu7KPrSNaoV_9QJo3mLop0,196
8
- Env/base_env.py,sha256=ORM6U5qwj7cTuSHFtSmCSsE0cl6pZ28D97CEyyFnucI,1323
9
- Env/base_executor.py,sha256=awTwJ44CKWV4JO2KUHfHDX0p1Ujw55hlaL5oNYTEW9M,893
10
- Env/env.py,sha256=I3lOoyBJR5QNq3tWk_HVH6ncRx1vnilRYmj7b7h4jic,114
11
- Env/js_executor.py,sha256=tEAg5ho8Pa8LzxUbS1Idau8XuJWZZqPiNlvFPDwGkgc,2690
12
- Env/python_executor.py,sha256=KiQxyxLgdEph1iRBLteqYy6KJGE8ag_jxiYJ_-BEEB4,3221
13
- Env/shell.py,sha256=gr6czmeuSWtB3xSA9TZN7wnK2BENOuA9zjNttwbxztU,1877
14
- Tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- Tools/file_task.py,sha256=VUhWq_G-SWvGahQo8PG7TOpElHUW3BGLUabrTdJS89o,12151
16
- Tools/system_details.py,sha256=7-mTm3CG4NoatHcvcosalEgEcpWlNsCsZ7kuS3y_EmY,2262
17
- Tools/tool_dir.json,sha256=1zF0Z8ATZn5GT5lGq0cGRlBQoh0F655RP_htkZwnJHI,2302
18
- Tools/tool_manager.py,sha256=0i3bd_VxhbpWKLzyfSeYyv_33Z6HmvQDBUxPUxjLYlU,1736
19
- Tools/userinp.py,sha256=vUhEj3y1W1_ZFHqo2xQwvqDyeOg3VsisSKTI0EurUH8,1205
20
- Tools/web_loader.py,sha256=PyZk2g7WngZT0tCLs9Danx20dYspnaZwy4rlVE9Sx_4,5054
21
- Tools/web_search.py,sha256=4EGq1VZqfDgG-_yXTd4_Ha1iEUcR-szdlgRV7oFPru4,1259
22
- Utils/__init__.py,sha256=oukU0ufroPRd8_N8d2xiFes9CTxSaw4NA6p2nS1kkSg,16
23
- Utils/executor_utils.py,sha256=eVVxZDcAGxENRV5DhCkgh1AKiJEgfV_1q-iSQNWR5kI,1180
24
- Utils/ter_interface.py,sha256=Zay9mwyAyKYTNQAKOWXHAa3upo9TWprSf26STiHXk0g,6255
25
- llm_interface/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
- llm_interface/llm.py,sha256=tI_KDOW14QLWowA7bB3GPe2qjlk0sjS5fBavs9XD1fo,5185
27
- pikoai-0.1.8.dist-info/licenses/LICENSE,sha256=cELUVOboOAderKFp8bdtcM5VyJi61YH1oDbRhOuoQZw,1067
28
- pikoai-0.1.8.dist-info/METADATA,sha256=BtVoSGxZ_-ZKZ7k1rgSEvkcCsj_5OpHokiHvOOnBkGU,2961
29
- pikoai-0.1.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
30
- pikoai-0.1.8.dist-info/entry_points.txt,sha256=xjZnheDymNDnQ0o84R0jZKEITrhNbzQWN-AhqfA_d6s,50
31
- pikoai-0.1.8.dist-info/top_level.txt,sha256=hWzBNE7UQsuNcENIOksGcJED08k3ZGRRn2X5jnStICU,53
32
- pikoai-0.1.8.dist-info/RECORD,,