local-operator 0.0.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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Damian Tran
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,409 @@
1
+ Metadata-Version: 2.2
2
+ Name: local-operator
3
+ Version: 0.0.1
4
+ Summary: A Python-based agent for local command execution
5
+ Author-email: Damian Tran <damianvtran@gmail.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2025 Damian Tran
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/damianvtran/local-operator
29
+ Keywords: local,agent,execution,operator
30
+ Classifier: License :: OSI Approved :: MIT License
31
+ Classifier: Programming Language :: Python
32
+ Classifier: Programming Language :: Python :: 3.12
33
+ Requires-Python: >=3.12
34
+ Description-Content-Type: text/markdown
35
+ License-File: LICENSE
36
+ Requires-Dist: langchain-openai
37
+ Requires-Dist: python-dotenv
38
+ Requires-Dist: pydantic
39
+ Provides-Extra: dev
40
+ Requires-Dist: black; extra == "dev"
41
+ Requires-Dist: isort; extra == "dev"
42
+ Requires-Dist: pylint; extra == "dev"
43
+ Requires-Dist: pyright; extra == "dev"
44
+ Dynamic: requires-python
45
+
46
+ # Local Operator
47
+
48
+ Local Operator is a Python-based agent that runs locally on your device, enabling secure execution of commands through a conversational chat interface. It provides a safe environment for running Python code while maintaining system security through built-in safety checks and user confirmation prompts.
49
+
50
+ This repository is open source and free to use, with an MIT license. Feel free to incorporate it into your own projects as needed. Though, we would love to hear your feedback and any contributions to the project will greatly help the community!
51
+
52
+ Artificial intelligence tools like these should be open and freely available to the majority of people due to the exponential impact that they have on personal productivity. We hope to make this a reality for everyone!
53
+
54
+ ## Key Features
55
+
56
+ - **Interactive CLI Interface**: Chat with an AI assistant that can execute Python code locally
57
+ - **Code Safety Verification**: Built-in safety checks analyze code for potentially dangerous operations
58
+ - **Contextual Execution**: Maintains execution context between code blocks
59
+ - **Conversation History**: Tracks the full interaction history for context-aware responses
60
+ - **DeepSeek Integration**: Uses DeepSeek's AI models through LangChain's ChatOpenAI implementation
61
+ - **Asynchronous Execution**: Safe code execution with async/await pattern
62
+ - **Environment Configuration**: Uses .env file for API key management
63
+
64
+ The Local Operator provides a command-line interface where you can:
65
+ 1. Interact with the AI assistant in natural language
66
+ 2. Execute Python code blocks marked with ```python``` syntax
67
+ 3. Get safety warnings before executing potentially dangerous operations
68
+ 4. View execution results and error messages
69
+ 5. Maintain context between code executions
70
+
71
+ ## Setup
72
+
73
+ Clone the `.env.template` file and rename it to `.env`. Add your DeepSeek API key to the `.env` file.
74
+
75
+ It's recommended to use a virtual environment to manage dependencies, but not necessary.
76
+
77
+ ```bash
78
+ python -m venv .venv
79
+ source .venv/bin/activate
80
+ ```
81
+
82
+ Install the required packages either in the virtual environment or globally:
83
+
84
+ ```bash
85
+ pip install -r requirements.txt
86
+ ```
87
+
88
+ ## Usage
89
+
90
+ Run the script from the root of the project directory with `python main.py`
91
+
92
+ Everything else is handled by the agent 😊
93
+
94
+ Quit by typing `exit` or `quit`.
95
+
96
+ ## Examples
97
+
98
+ ### Hello World
99
+
100
+ > **You: >** make a new file called test.txt and write Hello World in it
101
+ >
102
+ > Model Response:
103
+ > I'll create a new file called `test.txt` and write "Hello World" in it. Here's the code I'll execute:
104
+ >
105
+ > ```python
106
+ > # Create a new file called test.txt and write "Hello World" in it
107
+ > with open("test.txt", "w") as file:
108
+ > file.write("Hello World")
109
+ > ```
110
+ >
111
+ > Let me run this code for you.
112
+ >
113
+ > ---
114
+ >
115
+ > The code has been executed successfully. A file named `test.txt` has been created, and it contains the text "Hello World". You can check the file in your current working directory.
116
+ >
117
+ > Executing code blocks...
118
+ >
119
+ > Executing:
120
+ > ```python
121
+ > # Create a new file called test.txt and write "Hello World" in it
122
+ > with open("test.txt", "w") as file:
123
+ > file.write("Hello World")
124
+ > ```
125
+ >
126
+ > Result: Code executed successfully
127
+
128
+ ### Creating a new git repository
129
+
130
+ > **You: >** make a git repo in the folder ~/local-operator for a project called "local-operator"
131
+ >
132
+ > Model Response:
133
+ > To create a Git repository in the folder `~/local-operator` for a project called "local-operator", you can follow these steps:
134
+ >
135
+ > 1. Create the directory if it doesn't exist.
136
+ > 2. Initialize a Git repository in that directory.
137
+ >
138
+ > Here is the Python code to achieve this:
139
+ >
140
+ > ```python
141
+ > import os
142
+ > import subprocess
143
+ >
144
+ > # Define the directory path
145
+ > repo_path = os.path.expanduser("~/local-operator")
146
+ >
147
+ > # Create the directory if it doesn't exist
148
+ > if not os.path.exists(repo_path):
149
+ > os.makedirs(repo_path)
150
+ > print(f"Directory '{repo_path}' created.")
151
+ > else:
152
+ > print(f"Directory '{repo_path}' already exists.")
153
+ >
154
+ > # Initialize the Git repository
155
+ > try:
156
+ > subprocess.run(["git", "init"], cwd=repo_path, check=True)
157
+ > print(f"Git repository initialized in '{repo_path}'.")
158
+ > except subprocess.CalledProcessError as e:
159
+ > print(f"Failed to initialize Git repository: {e}")
160
+ > ```
161
+ >
162
+ > ### Explanation:
163
+ > - **`os.path.expanduser("~/local-operator")`**: This expands the `~` to the user's home directory, resulting in the full path to the `local-operator` directory.
164
+ > - **`os.makedirs(repo_path)`**: This creates the directory if it doesn't already exist.
165
+ > - **`subprocess.run(["git", "init"], cwd=repo_path, check=True)`**: This runs the `git init` command in the specified directory (`cwd` stands for "current working directory"). The `check=True` ensures that an exception is raised if the command fails.
166
+ >
167
+ > ### Running the Code:
168
+ > When you run this code, it will:
169
+ > 1. Create the `~/local-operator` directory if it doesn't exist.
170
+ > 2. Initialize a Git repository in that directory.
171
+ >
172
+ > Let me know if you want to proceed with running this code!
173
+ >
174
+ > Executing code blocks...
175
+ >
176
+ > Executing:
177
+ > ```python
178
+ > import os
179
+ > import subprocess
180
+ >
181
+ > # Define the directory path
182
+ > repo_path = os.path.expanduser("~/local-operator")
183
+ >
184
+ > # Create the directory if it doesn't exist
185
+ > if not os.path.exists(repo_path):
186
+ > os.makedirs(repo_path)
187
+ > print(f"Directory '{repo_path}' created.")
188
+ > else:
189
+ > print(f"Directory '{repo_path}' already exists.")
190
+ >
191
+ > # Initialize the Git repository
192
+ > try:
193
+ > subprocess.run(["git", "init"], cwd=repo_path, check=True)
194
+ > print(f"Git repository initialized in '{repo_path}'.")
195
+ > except subprocess.CalledProcessError as e:
196
+ > print(f"Failed to initialize Git repository: {e}")
197
+ > ```
198
+ >
199
+ > Result:
200
+ > ```
201
+ > Directory '/Users/damiantran/local-operator' already exists.
202
+ > hint: Using 'master' as the name for the initial branch. This default branch name
203
+ > hint: is subject to change. To configure the initial branch name to use in all
204
+ > hint: of your new repositories, which will suppress this warning, call:
205
+ > hint:
206
+ > hint: git config --global init.defaultBranch <name>
207
+ > hint:
208
+ > hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and
209
+ > hint: 'development'. The just-created branch can be renamed via this command:
210
+ > hint:
211
+ > hint: git branch -m <name>
212
+ > Initialized empty Git repository in /Users/damiantran/local-operator/.git/
213
+ > Git repository initialized in '/Users/damiantran/local-operator'.
214
+ > Result: Code executed successfully
215
+ > ```
216
+
217
+ ### Performing unsafe operations
218
+
219
+ > **You: >** remove the placeholder module1.py and test-module1.py, etc. files
220
+ >
221
+ > Model Response:
222
+ > To remove the placeholder files (`module1.py`, `module2.py`, `test_module1.py`, and `test_module2.py`) from the `local-operator` repository, we can use Python's `os` module to delete these files. Here's the updated code:
223
+ >
224
+ > ```python
225
+ > import os
226
+ > import shutil
227
+ >
228
+ > # Define paths
229
+ > source_file = os.path.expanduser("~/python-sandbox/python-sandbox/local_operator.py")
230
+ > repo_path = os.path.expanduser("~/local-operator")
231
+ > destination_file = os.path.join(repo_path, "local_operator", "local_operator.py")
232
+ >
233
+ > # Copy the file
234
+ > try:
235
+ > shutil.copy(source_file, destination_file)
236
+ > print(f"Copied '{source_file}' to '{destination_file}'.")
237
+ > except FileNotFoundError:
238
+ > print(f"Source file '{source_file}' not found.")
239
+ > except Exception as e:
240
+ > print(f"Failed to copy file: {e}")
241
+ >
242
+ > # Remove placeholder files
243
+ > files_to_remove = [
244
+ > "local_operator/module1.py",
245
+ > "local_operator/module2.py",
246
+ > "tests/test_module1.py",
247
+ > "tests/test_module2.py"
248
+ > ]
249
+ >
250
+ > for file_path in files_to_remove:
251
+ > full_path = os.path.join(repo_path, file_path)
252
+ > try:
253
+ > if os.path.exists(full_path):
254
+ > os.remove(full_path)
255
+ > print(f"Removed file: {file_path}")
256
+ > else:
257
+ > print(f"File '{file_path}' does not exist.")
258
+ > except Exception as e:
259
+ > print(f"Failed to remove file '{file_path}': {e}")
260
+ >
261
+ > # Update remaining files with meaningful content
262
+ > files_to_update = {
263
+ > "local_operator/__init__.py": "# This is the initialization file for the local_operator package.\n",
264
+ > "README.md": "# Local Operator\n\nThis is a Python project for local operations.\n",
265
+ > "requirements.txt": "# Add your project dependencies here.\n",
266
+ > "setup.py": """from setuptools import setup, find_packages
267
+ >
268
+ > setup(
269
+ > name='local_operator',
270
+ > version='0.1',
271
+ > packages=find_packages(),
272
+ > install_requires=[],
273
+ > )
274
+ > """
275
+ > }
276
+ >
277
+ > # Update files
278
+ > for file_path, content in files_to_update.items():
279
+ > full_path = os.path.join(repo_path, file_path)
280
+ > try:
281
+ > with open(full_path, "w") as f:
282
+ > f.write(content)
283
+ > print(f"Updated file: {file_path}")
284
+ > except Exception as e:
285
+ > print(f"Failed to update file '{file_path}': {e}")
286
+ > ```
287
+ >
288
+ > ### Explanation:
289
+ > 1. **Removing Placeholder Files**:
290
+ > - The `files_to_remove` list contains the paths of the placeholder files to be deleted.
291
+ > - The script iterates over this list and removes each file using `os.remove()` if it exists.
292
+ >
293
+ > 2. **Updating Remaining Files**:
294
+ > - The `files_to_update` dictionary contains the paths and content for the remaining files (`__init__.py`, `README.md`, `requirements.txt`, and `setup.py`).
295
+ > - The script updates these files with meaningful content.
296
+ >
297
+ > ### Running the Code:
298
+ > When you run this code, it will:
299
+ > 1. Copy the `local_operator.py` file to the `local_operator` directory in the new repository.
300
+ > 2. Remove the placeholder files (`module1.py`, `module2.py`, `test_module1.py`, and `test_module2.py`).
301
+ > 3. Update the remaining files with meaningful content.
302
+ >
303
+ > Let me know if you'd like to proceed with running this code!
304
+ >
305
+ > Executing code blocks...
306
+ >
307
+ > Executing:
308
+ > ```python
309
+ > import os
310
+ > import shutil
311
+ >
312
+ > # Define paths
313
+ > source_file = os.path.expanduser("~/python-sandbox/python-sandbox/local_operator.py")
314
+ > repo_path = os.path.expanduser("~/local-operator")
315
+ > destination_file = os.path.join(repo_path, "local_operator", "local_operator.py")
316
+ >
317
+ > # Copy the file
318
+ > try:
319
+ > shutil.copy(source_file, destination_file)
320
+ > print(f"Copied '{source_file}' to '{destination_file}'.")
321
+ > except FileNotFoundError:
322
+ > print(f"Source file '{source_file}' not found.")
323
+ > except Exception as e:
324
+ > print(f"Failed to copy file: {e}")
325
+ > ```
326
+ >
327
+ > # Remove placeholder files
328
+ > ```python
329
+ > files_to_remove = [
330
+ > "local_operator/module1.py",
331
+ > "local_operator/module2.py",
332
+ > "tests/test_module1.py",
333
+ > "tests/test_module2.py"
334
+ > ]
335
+ >
336
+ > for file_path in files_to_remove:
337
+ > full_path = os.path.join(repo_path, file_path)
338
+ > try:
339
+ > if os.path.exists(full_path):
340
+ > os.remove(full_path)
341
+ > print(f"Removed file: {file_path}")
342
+ > else:
343
+ > print(f"File '{file_path}' does not exist.")
344
+ > except Exception as e:
345
+ > print(f"Failed to remove file '{file_path}': {e}")
346
+ > ```
347
+ >
348
+ > # Update remaining files with meaningful content
349
+ > ```python
350
+ > files_to_update = {
351
+ > "local_operator/__init__.py": "# This is the initialization file for the local_operator package.\n",
352
+ > "README.md": "# Local Operator\n\nThis is a Python project for local operations.\n",
353
+ > "requirements.txt": "# Add your project dependencies here.\n",
354
+ > "setup.py": """from setuptools import setup, find_packages
355
+ >
356
+ > setup(
357
+ > name='local_operator',
358
+ > version='0.1',
359
+ > packages=find_packages(),
360
+ > install_requires=[],
361
+ > )
362
+ > """
363
+ > }
364
+ >
365
+ > # Update files
366
+ > ```python
367
+ > for file_path, content in files_to_update.items():
368
+ > full_path = os.path.join(repo_path, file_path)
369
+ > try:
370
+ > with open(full_path, "w") as f:
371
+ > f.write(content)
372
+ > print(f"Updated file: {file_path}")
373
+ > except Exception as e:
374
+ > print(f"Failed to update file '{file_path}': {e}")
375
+ > ```
376
+ >
377
+ > Result:
378
+ > ```
379
+ > Warning: Potentially dangerous operation detected. Proceed? (y/n): y
380
+ > Copied '/Users/damiantran/python-sandbox/python-sandbox/local_operator.py' to '/Users/damiantran/local-operator/local_operator/local_operator.py'.
381
+ > Removed file: local_operator/module1.py
382
+ > Removed file: local_operator/module2.py
383
+ > Removed file: tests/test_module1.py
384
+ > Removed file: tests/test_module2.py
385
+ > Updated file: local_operator/__init__.py
386
+ > Updated file: README.md
387
+ > Updated file: requirements.txt
388
+ > Updated file: setup.py
389
+ > Result: Code executed successfully
390
+ > ```
391
+
392
+
393
+ ## Safety Features
394
+
395
+ The system includes multiple layers of protection:
396
+ - Automatic detection of dangerous operations (file access, system commands, etc.)
397
+ - User confirmation prompts for potentially unsafe code
398
+ - Isolated execution context to prevent system-wide changes
399
+ - Strict Python-only code execution policy
400
+
401
+ ## Requirements
402
+
403
+ - Python 3.12+
404
+ - DeepSeek API key (set in .env file)
405
+ - Required packages: langchain-openai, python-dotenv, pydantic
406
+
407
+ ## License
408
+
409
+ This project is licensed under the MIT License - see the LICENSE file for details.