penguin-agent-package 0.1.0__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.
- penguin_agent_package-0.1.0/.github/workflows/python-app.yml +39 -0
- penguin_agent_package-0.1.0/.gitignore +4 -0
- penguin_agent_package-0.1.0/.idea/.gitignore +5 -0
- penguin_agent_package-0.1.0/.idea/.name +1 -0
- penguin_agent_package-0.1.0/.idea/inspectionProfiles/profiles_settings.xml +6 -0
- penguin_agent_package-0.1.0/.idea/micropython-tools-settings.xml +12 -0
- penguin_agent_package-0.1.0/.idea/misc.xml +4 -0
- penguin_agent_package-0.1.0/.idea/modules.xml +9 -0
- penguin_agent_package-0.1.0/.idea/penguin-agent-package.iml +11 -0
- penguin_agent_package-0.1.0/.idea/penguin-agent.iml +11 -0
- penguin_agent_package-0.1.0/.idea/shelf/Uncommitted_changes_before_Update_at_09_10_26,_21_02_[Changes]/shelved.patch +207 -0
- penguin_agent_package-0.1.0/.idea/shelf/Uncommitted_changes_before_Update_at_09_10_26__21_02__Changes_.xml +4 -0
- penguin_agent_package-0.1.0/.idea/vcs.xml +6 -0
- penguin_agent_package-0.1.0/.idea/workspace.xml +164 -0
- penguin_agent_package-0.1.0/LICENSE +21 -0
- penguin_agent_package-0.1.0/PKG-INFO +82 -0
- penguin_agent_package-0.1.0/README.md +51 -0
- penguin_agent_package-0.1.0/pyproject.toml +40 -0
- penguin_agent_package-0.1.0/requirements.txt +20 -0
- penguin_agent_package-0.1.0/ressources/bildschirmaufzeichnung-vom-2026-09-26-23-34-26_q3iOhA72 (online-video-cutter.com).gif +0 -0
- penguin_agent_package-0.1.0/ressources/bildschirmaufzeichnung-vom-2026-09-26-23-34-26_q3iOhA72 (online-video-cutter.com).mp4 +0 -0
- penguin_agent_package-0.1.0/src/penguin-agent/__main__.py +159 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# This workflow will install Python dependencies, run tests and lint with a single version of Python
|
|
2
|
+
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
|
|
3
|
+
|
|
4
|
+
name: Python application
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
branches: [ "main" ]
|
|
9
|
+
pull_request:
|
|
10
|
+
branches: [ "main" ]
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
build:
|
|
17
|
+
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
- name: Set up Python 3.10
|
|
23
|
+
uses: actions/setup-python@v3
|
|
24
|
+
with:
|
|
25
|
+
python-version: "3.10"
|
|
26
|
+
- name: Install dependencies
|
|
27
|
+
run: |
|
|
28
|
+
python -m pip install --upgrade pip
|
|
29
|
+
pip install flake8 pytest
|
|
30
|
+
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
|
|
31
|
+
- name: Lint with flake8
|
|
32
|
+
run: |
|
|
33
|
+
# stop the build if there are Python syntax errors or undefined names
|
|
34
|
+
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
|
|
35
|
+
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
|
|
36
|
+
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
|
37
|
+
- name: Test with pytest
|
|
38
|
+
run: |
|
|
39
|
+
pytest
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
penguin-agent
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<project version="4">
|
|
3
|
+
<component name="MicroPythonTools">
|
|
4
|
+
<option name="activeWebReplPresetId" value="49e95b1f-e1d8-4cbd-bdfe-b41d2aa63ece" />
|
|
5
|
+
<option name="webReplPresets">
|
|
6
|
+
<map>
|
|
7
|
+
<entry key="49e95b1f-e1d8-4cbd-bdfe-b41d2aa63ece" value="{"id":"49e95b1f-e1d8-4cbd-bdfe-b41d2aa63ece","name":"Default"}" />
|
|
8
|
+
</map>
|
|
9
|
+
</option>
|
|
10
|
+
<option name="webReplPresetsMigrated" value="true" />
|
|
11
|
+
</component>
|
|
12
|
+
</project>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<project version="4">
|
|
3
|
+
<component name="ProjectModuleManager">
|
|
4
|
+
<modules>
|
|
5
|
+
<module fileurl="file://$PROJECT_DIR$/.idea/penguin.iml" filepath="$PROJECT_DIR$/.idea/penguin.iml" />
|
|
6
|
+
<module fileurl="file://$PROJECT_DIR$/.idea/penguin-agent-package.iml" filepath="$PROJECT_DIR$/.idea/penguin-agent-package.iml" />
|
|
7
|
+
</modules>
|
|
8
|
+
</component>
|
|
9
|
+
</project>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<module external.system.id="pyproject.toml" type="PYTHON_MODULE" version="4">
|
|
3
|
+
<component name="NewModuleRootManager">
|
|
4
|
+
<content url="file://$MODULE_DIR$">
|
|
5
|
+
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
|
6
|
+
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
|
7
|
+
</content>
|
|
8
|
+
<orderEntry type="jdk" jdkName="~/Dokumente/GitHub/penguin-agent/.venv" jdkType="Python SDK" />
|
|
9
|
+
<orderEntry type="sourceFolder" forTests="false" />
|
|
10
|
+
</component>
|
|
11
|
+
</module>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<module external.system.id="pyproject.toml" type="PYTHON_MODULE" version="4">
|
|
3
|
+
<component name="NewModuleRootManager">
|
|
4
|
+
<content url="file://$MODULE_DIR$">
|
|
5
|
+
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
|
6
|
+
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
|
7
|
+
</content>
|
|
8
|
+
<orderEntry type="jdk" jdkName="~/Dokumente/GitHub/penguin/.venv" jdkType="Python SDK" />
|
|
9
|
+
<orderEntry type="sourceFolder" forTests="false" />
|
|
10
|
+
</component>
|
|
11
|
+
</module>
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
Index: src/penguin/__main__.py
|
|
2
|
+
IDEA additional info:
|
|
3
|
+
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
|
4
|
+
<+>UTF-8
|
|
5
|
+
===================================================================
|
|
6
|
+
diff --git a/src/penguin/__main__.py b/src/penguin-agent/__main__.py
|
|
7
|
+
rename from src/penguin/__main__.py
|
|
8
|
+
rename to src/penguin-agent/__main__.py
|
|
9
|
+
--- a/src/penguin/__main__.py (revision f229aff10363c0cd6431cf8c21ec24e207be1677)
|
|
10
|
+
+++ b/src/penguin-agent/__main__.py (date 1791565163364)
|
|
11
|
+
@@ -12,7 +12,7 @@
|
|
12
|
+
import json
|
|
13
|
+
import datetime
|
|
14
|
+
import os
|
|
15
|
+
-ollamaclient = Client(host="http://192.168.2.127:11434")
|
|
16
|
+
+ollamaclient = Client(host=os.environ['OLLAMA_BASE_URL'])
|
|
17
|
+
spinner = animation.Wait('spinner', text='penguin is working')
|
|
18
|
+
|
|
19
|
+
def execute_bash_command(command: str) -> str:
|
|
20
|
+
@@ -23,15 +23,24 @@
|
|
21
|
+
|
|
22
|
+
spinner.stop()
|
|
23
|
+
print("Using bash command: ")
|
|
24
|
+
- result = subprocess.run(command, cwd="/home/ferdinand", shell=True, capture_output=True, text=True)
|
|
25
|
+
try:
|
|
26
|
+
- print_formatted_text(HTML(f'<violet>{str(command)}</violet>'))
|
|
27
|
+
- print_formatted_text(HTML(f'<seagreen>{str(result.stdout.strip())}</seagreen>'))
|
|
28
|
+
- except:
|
|
29
|
+
- print('Printing command output was aborted. Command was executed')
|
|
30
|
+
+ result = subprocess.run(command, check=True, cwd="/home/ferdinand", shell=True, capture_output=True, text=True)
|
|
31
|
+
+ try:
|
|
32
|
+
+ print_formatted_text(HTML(f'<violet>{str(command)}</violet>'))
|
|
33
|
+
+ print_formatted_text(HTML(f'<seagreen>{str(result.stdout.strip())}</seagreen>'))
|
|
34
|
+
+ except:
|
|
35
|
+
+ print(command)
|
|
36
|
+
+ print(result.stdout.strip())
|
|
37
|
+
|
|
38
|
+
- spinner.start()
|
|
39
|
+
- return result.stdout.strip()
|
|
40
|
+
+ spinner.start()
|
|
41
|
+
+ return result.stdout.strip()
|
|
42
|
+
+ except subprocess.CalledProcessError as e:
|
|
43
|
+
+ print(command)
|
|
44
|
+
+ print('error')
|
|
45
|
+
+ spinner.start()
|
|
46
|
+
+ return e.stderr.strip()
|
|
47
|
+
+
|
|
48
|
+
+
|
|
49
|
+
|
|
50
|
+
def get_memory_entry(keyword: str) -> str:
|
|
51
|
+
"""Get memory entries by keyword. The tool will return all the memory entries matching the keyword. Use this tool when searching for a solution for problem you already solved.
|
|
52
|
+
@@ -83,11 +92,8 @@
|
|
53
|
+
tool_dict = {"execute_bash_command": execute_bash_command, "enter_memory_entry": enter_memory_entry, "get_memory_entry": get_memory_entry}
|
|
54
|
+
messages_memory = [{'role': 'system', 'content': "You are a linux ai-assistant called penguin. You can execute bash commands and use your memory to help the user completiting tasks. ALWAYS provide a final answer WITHOUT tool calls to inform he user baout your results."}]
|
|
55
|
+
messages = []
|
|
56
|
+
-custom_style = Style.from_dict({
|
|
57
|
+
- '': 'bg:#2c3e50 fg:#ecf0f1', # Hintergrund dunkelblau, Text weiß
|
|
58
|
+
- 'prompt': 'bg:#2980b9 fg:#ffffff bold',
|
|
59
|
+
-})
|
|
60
|
+
-session = PromptSession(history=FileHistory(".history"), bottom_toolbar=bottom_toolbar, style=custom_style)
|
|
61
|
+
+
|
|
62
|
+
+session = PromptSession(history=FileHistory(".history"), bottom_toolbar=bottom_toolbar)
|
|
63
|
+
def run_agent(message, maximal_num_turns):
|
|
64
|
+
global messages_memory
|
|
65
|
+
global messages
|
|
66
|
+
@@ -137,7 +143,10 @@
|
|
67
|
+
print('Exiting penguin.')
|
|
68
|
+
break
|
|
69
|
+
else:
|
|
70
|
+
- typewrite(run_agent(user_input, 30), speed=0.01)
|
|
71
|
+
+ try:
|
|
72
|
+
+ typewrite(run_agent(user_input, 30), speed=0.01)
|
|
73
|
+
+ except:
|
|
74
|
+
+ print_formatted_text(HTML(f'<red>An error occurred during the agent loop. Please note that this project is currently in development and if it does not work, DO IT YOURSELF</red>'))
|
|
75
|
+
|
|
76
|
+
if __name__ == "__main__":
|
|
77
|
+
main()
|
|
78
|
+
|
|
79
|
+
Index: .gitignore
|
|
80
|
+
IDEA additional info:
|
|
81
|
+
Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP
|
|
82
|
+
<+>\n/src/.history\n/src/penguin/memory.json\n
|
|
83
|
+
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
|
84
|
+
<+>UTF-8
|
|
85
|
+
===================================================================
|
|
86
|
+
diff --git a/.gitignore b/.gitignore
|
|
87
|
+
--- a/.gitignore (revision f229aff10363c0cd6431cf8c21ec24e207be1677)
|
|
88
|
+
+++ b/.gitignore (date 1791563907743)
|
|
89
|
+
@@ -1,3 +1,4 @@
|
|
90
|
+
|
|
91
|
+
/src/.history
|
|
92
|
+
-/src/penguin/memory.json
|
|
93
|
+
+*.json
|
|
94
|
+
+*.env
|
|
95
|
+
|
|
96
|
+
Index: pyproject.toml
|
|
97
|
+
IDEA additional info:
|
|
98
|
+
Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP
|
|
99
|
+
<+>[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\"\n\n[project]\nname = \"penguin\"\nversion = \"0.1.0\"\ndescription = \"Simple, minimalistic AI-Agent for Linux that runs on almost every device\"\nreadme = \"README.md\"\nrequires-python = \">=3.12\"\ndependencies = [\n \"animation>=0.0.7\",\n \"annotated-types>=0.8.0\",\n \"anyio>=4.15.1\",\n \"certifi>=2026.7.22\",\n \"ghostprint>=2.0.0\",\n \"h11>=0.16.0\",\n \"httpcore>=1.0.9\",\n \"httpx>=0.28.1\",\n \"idna>=3.20\",\n \"ollama>=0.6.2\",\n \"prompt_toolkit>=3.0.53\",\n \"pychalk>=2.0.1\",\n \"pydantic>=2.13.5\",\n \"pydantic_core>=2.46.5\",\n \"six>=1.17.0\",\n \"typing-inspection>=0.4.4\",\n \"typing_extensions>=4.16.0\",\n \"wcwidth>=0.9.1\",\n]\n
|
|
100
|
+
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
|
101
|
+
<+>UTF-8
|
|
102
|
+
===================================================================
|
|
103
|
+
diff --git a/pyproject.toml b/pyproject.toml
|
|
104
|
+
--- a/pyproject.toml (revision f229aff10363c0cd6431cf8c21ec24e207be1677)
|
|
105
|
+
+++ b/pyproject.toml (date 1791572509991)
|
|
106
|
+
@@ -1,13 +1,22 @@
|
|
107
|
+
[build-system]
|
|
108
|
+
requires = ["hatchling"]
|
|
109
|
+
build-backend = "hatchling.build"
|
|
110
|
+
-
|
|
111
|
+
+[tool.hatch.build.targets.wheel]
|
|
112
|
+
+packages = ["src/penguin"]
|
|
113
|
+
[project]
|
|
114
|
+
-name = "penguin"
|
|
115
|
+
+name = "penguin-agent-package"
|
|
116
|
+
version = "0.1.0"
|
|
117
|
+
description = "Simple, minimalistic AI-Agent for Linux that runs on almost every device"
|
|
118
|
+
readme = "README.md"
|
|
119
|
+
requires-python = ">=3.12"
|
|
120
|
+
+classifiers = [
|
|
121
|
+
+ "Programming Language :: Python :: 3",
|
|
122
|
+
+ "Operating System :: OS Independent",
|
|
123
|
+
+]
|
|
124
|
+
+license = "MIT"
|
|
125
|
+
+authors = [
|
|
126
|
+
+ { name="theobalduser", email="fzierenberg@icloud.com" },
|
|
127
|
+
+]
|
|
128
|
+
dependencies = [
|
|
129
|
+
"animation>=0.0.7",
|
|
130
|
+
"annotated-types>=0.8.0",
|
|
131
|
+
Index: .idea/penguin-agent.iml
|
|
132
|
+
IDEA additional info:
|
|
133
|
+
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
|
134
|
+
<+>UTF-8
|
|
135
|
+
===================================================================
|
|
136
|
+
diff --git a/.idea/penguin-agent.iml b/.idea/penguin-agent.iml
|
|
137
|
+
new file mode 100644
|
|
138
|
+
--- /dev/null (date 1791563166628)
|
|
139
|
+
+++ b/.idea/penguin-agent.iml (date 1791563166628)
|
|
140
|
+
@@ -0,0 +1,11 @@
|
|
141
|
+
+<?xml version="1.0" encoding="UTF-8"?>
|
|
142
|
+
+<module external.system.id="pyproject.toml" type="PYTHON_MODULE" version="4">
|
|
143
|
+
+ <component name="NewModuleRootManager">
|
|
144
|
+
+ <content url="file://$MODULE_DIR$">
|
|
145
|
+
+ <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
|
146
|
+
+ <excludeFolder url="file://$MODULE_DIR$/.venv" />
|
|
147
|
+
+ </content>
|
|
148
|
+
+ <orderEntry type="jdk" jdkName="~/Dokumente/GitHub/penguin/.venv" jdkType="Python SDK" />
|
|
149
|
+
+ <orderEntry type="sourceFolder" forTests="false" />
|
|
150
|
+
+ </component>
|
|
151
|
+
+</module>
|
|
152
|
+
|
|
153
|
+
Index: .idea/modules.xml
|
|
154
|
+
IDEA additional info:
|
|
155
|
+
Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP
|
|
156
|
+
<+><?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n <component name=\"ProjectModuleManager\">\n <modules>\n <module fileurl=\"file://$PROJECT_DIR$/.idea/penguin.iml\" filepath=\"$PROJECT_DIR$/.idea/penguin.iml\" />\n </modules>\n </component>\n</project>
|
|
157
|
+
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
|
158
|
+
<+>UTF-8
|
|
159
|
+
===================================================================
|
|
160
|
+
diff --git a/.idea/modules.xml b/.idea/modules.xml
|
|
161
|
+
--- a/.idea/modules.xml (revision f229aff10363c0cd6431cf8c21ec24e207be1677)
|
|
162
|
+
+++ b/.idea/modules.xml (date 1791569063107)
|
|
163
|
+
@@ -3,6 +3,7 @@
|
|
164
|
+
<component name="ProjectModuleManager">
|
|
165
|
+
<modules>
|
|
166
|
+
<module fileurl="file://$PROJECT_DIR$/.idea/penguin.iml" filepath="$PROJECT_DIR$/.idea/penguin.iml" />
|
|
167
|
+
+ <module fileurl="file://$PROJECT_DIR$/.idea/penguin@1.iml" filepath="$PROJECT_DIR$/.idea/penguin@1.iml" />
|
|
168
|
+
</modules>
|
|
169
|
+
</component>
|
|
170
|
+
</project>
|
|
171
|
+
|
|
172
|
+
Index: .idea/penguin@1.iml
|
|
173
|
+
IDEA additional info:
|
|
174
|
+
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
|
175
|
+
<+>UTF-8
|
|
176
|
+
===================================================================
|
|
177
|
+
diff --git a/.idea/penguin@1.iml b/.idea/penguin@1.iml
|
|
178
|
+
new file mode 100644
|
|
179
|
+
--- /dev/null (date 1791569069440)
|
|
180
|
+
+++ b/.idea/penguin@1.iml (date 1791569069440)
|
|
181
|
+
@@ -0,0 +1,11 @@
|
|
182
|
+
+<?xml version="1.0" encoding="UTF-8"?>
|
|
183
|
+
+<module external.system.id="pyproject.toml" type="PYTHON_MODULE" version="4">
|
|
184
|
+
+ <component name="NewModuleRootManager">
|
|
185
|
+
+ <content url="file://$MODULE_DIR$">
|
|
186
|
+
+ <sourceFolder url="file://$MODULE_DIR$/src" />
|
|
187
|
+
+ <excludeFolder url="file://$MODULE_DIR$/.venv" />
|
|
188
|
+
+ </content>
|
|
189
|
+
+ <orderEntry type="jdk" jdkName="~/Dokumente/GitHub/penguin-agent/.venv" jdkType="Python SDK" />
|
|
190
|
+
+ <orderEntry type="sourceFolder" forTests="false" />
|
|
191
|
+
+ </component>
|
|
192
|
+
+</module>
|
|
193
|
+
|
|
194
|
+
Index: .idea/.name
|
|
195
|
+
IDEA additional info:
|
|
196
|
+
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
|
197
|
+
<+>UTF-8
|
|
198
|
+
===================================================================
|
|
199
|
+
diff --git a/.idea/.name b/.idea/.name
|
|
200
|
+
new file mode 100644
|
|
201
|
+
--- /dev/null (date 1791568310002)
|
|
202
|
+
+++ b/.idea/.name (date 1791568310002)
|
|
203
|
+
@@ -0,0 +1,1 @@
|
|
204
|
+
+penguin-agent
|
|
205
|
+
|
|
206
|
+
diff --git a/src/penguin/memory.json b/src/penguin/memory.json
|
|
207
|
+
deleted file mode 100644
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<changelist name="Uncommitted_changes_before_Update_at_09_10_26,_21_02_[Changes]" date="1791572531088" recycled="true" deleted="true">
|
|
2
|
+
<option name="PATH" value="$PROJECT_DIR$/.idea/shelf/Uncommitted_changes_before_Update_at_09_10_26,_21_02_[Changes]/shelved.patch" />
|
|
3
|
+
<option name="DESCRIPTION" value="Uncommitted changes before Update at 09.10.26, 21:02 [Changes]" />
|
|
4
|
+
</changelist>
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<project version="4">
|
|
3
|
+
<component name="AutoImportSettings">
|
|
4
|
+
<option name="autoReloadType" value="SELECTIVE" />
|
|
5
|
+
</component>
|
|
6
|
+
<component name="ChangeListManager">
|
|
7
|
+
<list default="true" id="f864af2f-056a-4ed1-815d-52ec71aad92b" name="Changes" comment="README work - I'm sorry, I needed to test the gif rendering">
|
|
8
|
+
<change beforePath="$PROJECT_DIR$/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/README.md" afterDir="false" />
|
|
9
|
+
</list>
|
|
10
|
+
<option name="SHOW_DIALOG" value="false" />
|
|
11
|
+
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
|
12
|
+
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
|
13
|
+
<option name="LAST_RESOLUTION" value="IGNORE" />
|
|
14
|
+
</component>
|
|
15
|
+
<component name="Git.Settings">
|
|
16
|
+
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
|
17
|
+
</component>
|
|
18
|
+
<component name="GitHubPullRequestSearchHistory">{
|
|
19
|
+
"lastFilter": {
|
|
20
|
+
"state": "OPEN",
|
|
21
|
+
"assignee": "Test1-eu"
|
|
22
|
+
}
|
|
23
|
+
}</component>
|
|
24
|
+
<component name="GithubPullRequestsUISettings">{
|
|
25
|
+
"selectedUrlAndAccountId": {
|
|
26
|
+
"url": "https://github.com/Test1-eu/penguin.git",
|
|
27
|
+
"accountId": "64a69a75-d7ec-457f-ba10-9cc8d072f42c"
|
|
28
|
+
}
|
|
29
|
+
}</component>
|
|
30
|
+
<component name="ProjectColorInfo">{
|
|
31
|
+
"associatedIndex": 4,
|
|
32
|
+
"fromUser": false
|
|
33
|
+
}</component>
|
|
34
|
+
<component name="ProjectId" id="3JuzjoRMtUd4O46bRYlP6Iupp1j" />
|
|
35
|
+
<component name="ProjectLevelVcsManager">
|
|
36
|
+
<ConfirmationsSetting value="2" id="Add" />
|
|
37
|
+
</component>
|
|
38
|
+
<component name="ProjectViewState">
|
|
39
|
+
<option name="hideEmptyMiddlePackages" value="true" />
|
|
40
|
+
<option name="showLibraryContents" value="true" />
|
|
41
|
+
</component>
|
|
42
|
+
<component name="PropertiesComponent">{
|
|
43
|
+
"keyToString": {
|
|
44
|
+
"ASKED_SHARE_PROJECT_CONFIGURATION_FILES": "true",
|
|
45
|
+
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
|
46
|
+
"Python.__main__.executor": "Run",
|
|
47
|
+
"RunOnceActivity.ShowReadmeOnStart": "true",
|
|
48
|
+
"RunOnceActivity.git.unshallow": "true",
|
|
49
|
+
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
|
50
|
+
"SHARE_PROJECT_CONFIGURATION_FILES": "true",
|
|
51
|
+
"codeWithMe.voiceChat.enabledByDefault": "false",
|
|
52
|
+
"git-widget-placeholder": "main",
|
|
53
|
+
"last_opened_file_path": "/home/ferdinand/Dokumente/GitHub/penguin-agent",
|
|
54
|
+
"node.js.detected.package.jshint": "true",
|
|
55
|
+
"node.js.selected.package.jshint": "",
|
|
56
|
+
"nodejs_package_manager_path": "npm",
|
|
57
|
+
"python.packages.toolwindow.project.migrated.to.right.top.v2": "true",
|
|
58
|
+
"settings.editor.selected.configurable": "com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable"
|
|
59
|
+
}
|
|
60
|
+
}</component>
|
|
61
|
+
<component name="RecentsManager">
|
|
62
|
+
<key name="MoveFile.RECENT_KEYS">
|
|
63
|
+
<recent name="$PROJECT_DIR$/src/penguin-agent" />
|
|
64
|
+
<recent name="$PROJECT_DIR$/src" />
|
|
65
|
+
<recent name="$PROJECT_DIR$" />
|
|
66
|
+
<recent name="$PROJECT_DIR$/src/penguin" />
|
|
67
|
+
</key>
|
|
68
|
+
</component>
|
|
69
|
+
<component name="SharedIndexes">
|
|
70
|
+
<attachedChunks>
|
|
71
|
+
<set>
|
|
72
|
+
<option value="bundled-python-sdk-73a66231627c-5bd6d13a2a75-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-262.10968.92" />
|
|
73
|
+
</set>
|
|
74
|
+
</attachedChunks>
|
|
75
|
+
</component>
|
|
76
|
+
<component name="TaskManager">
|
|
77
|
+
<task active="true" id="Default" summary="Default task">
|
|
78
|
+
<changelist id="f864af2f-056a-4ed1-815d-52ec71aad92b" name="Changes" comment="" />
|
|
79
|
+
<created>1790525890318</created>
|
|
80
|
+
<option name="number" value="Default" />
|
|
81
|
+
<option name="presentableId" value="Default" />
|
|
82
|
+
<updated>1790525890318</updated>
|
|
83
|
+
</task>
|
|
84
|
+
<task id="LOCAL-00001" summary="Updated the documentation tool(deleted it)">
|
|
85
|
+
<option name="closed" value="true" />
|
|
86
|
+
<created>1790526540788</created>
|
|
87
|
+
<option name="number" value="00001" />
|
|
88
|
+
<option name="presentableId" value="LOCAL-00001" />
|
|
89
|
+
<option name="project" value="LOCAL" />
|
|
90
|
+
<updated>1790526540788</updated>
|
|
91
|
+
</task>
|
|
92
|
+
<task id="LOCAL-00002" summary="Created PyPi package structure">
|
|
93
|
+
<option name="closed" value="true" />
|
|
94
|
+
<created>1790528840278</created>
|
|
95
|
+
<option name="number" value="00002" />
|
|
96
|
+
<option name="presentableId" value="LOCAL-00002" />
|
|
97
|
+
<option name="project" value="LOCAL" />
|
|
98
|
+
<updated>1790528840278</updated>
|
|
99
|
+
</task>
|
|
100
|
+
<task id="LOCAL-00003" summary="Updated module structure">
|
|
101
|
+
<option name="closed" value="true" />
|
|
102
|
+
<created>1791045044511</created>
|
|
103
|
+
<option name="number" value="00003" />
|
|
104
|
+
<option name="presentableId" value="LOCAL-00003" />
|
|
105
|
+
<option name="project" value="LOCAL" />
|
|
106
|
+
<updated>1791045044512</updated>
|
|
107
|
+
</task>
|
|
108
|
+
<task id="LOCAL-00004" summary="Updated module structure, added gitignore">
|
|
109
|
+
<option name="closed" value="true" />
|
|
110
|
+
<created>1791046451210</created>
|
|
111
|
+
<option name="number" value="00004" />
|
|
112
|
+
<option name="presentableId" value="LOCAL-00004" />
|
|
113
|
+
<option name="project" value="LOCAL" />
|
|
114
|
+
<updated>1791046451210</updated>
|
|
115
|
+
</task>
|
|
116
|
+
<task id="LOCAL-00005" summary="Reworked README.md and added .env structure to __main__.py">
|
|
117
|
+
<option name="closed" value="true" />
|
|
118
|
+
<created>1791575739009</created>
|
|
119
|
+
<option name="number" value="00005" />
|
|
120
|
+
<option name="presentableId" value="LOCAL-00005" />
|
|
121
|
+
<option name="project" value="LOCAL" />
|
|
122
|
+
<updated>1791575739009</updated>
|
|
123
|
+
</task>
|
|
124
|
+
<task id="LOCAL-00006" summary="README work">
|
|
125
|
+
<option name="closed" value="true" />
|
|
126
|
+
<created>1791652283259</created>
|
|
127
|
+
<option name="number" value="00006" />
|
|
128
|
+
<option name="presentableId" value="LOCAL-00006" />
|
|
129
|
+
<option name="project" value="LOCAL" />
|
|
130
|
+
<updated>1791652283259</updated>
|
|
131
|
+
</task>
|
|
132
|
+
<task id="LOCAL-00007" summary="README work">
|
|
133
|
+
<option name="closed" value="true" />
|
|
134
|
+
<created>1791652381136</created>
|
|
135
|
+
<option name="number" value="00007" />
|
|
136
|
+
<option name="presentableId" value="LOCAL-00007" />
|
|
137
|
+
<option name="project" value="LOCAL" />
|
|
138
|
+
<updated>1791652381136</updated>
|
|
139
|
+
</task>
|
|
140
|
+
<task id="LOCAL-00008" summary="README work - I'm sorry, I needed to test the gif rendering">
|
|
141
|
+
<option name="closed" value="true" />
|
|
142
|
+
<created>1791652475862</created>
|
|
143
|
+
<option name="number" value="00008" />
|
|
144
|
+
<option name="presentableId" value="LOCAL-00008" />
|
|
145
|
+
<option name="project" value="LOCAL" />
|
|
146
|
+
<updated>1791652475862</updated>
|
|
147
|
+
</task>
|
|
148
|
+
<option name="localTasksCounter" value="9" />
|
|
149
|
+
<servers />
|
|
150
|
+
</component>
|
|
151
|
+
<component name="TypeScriptGeneratedFilesManager">
|
|
152
|
+
<option name="version" value="3" />
|
|
153
|
+
</component>
|
|
154
|
+
<component name="VcsManagerConfiguration">
|
|
155
|
+
<MESSAGE value="Updated the documentation tool(deleted it)" />
|
|
156
|
+
<MESSAGE value="Created PyPi package structure" />
|
|
157
|
+
<MESSAGE value="Updated module structure" />
|
|
158
|
+
<MESSAGE value="Updated module structure, added gitignore" />
|
|
159
|
+
<MESSAGE value="Reworked README.md and added .env structure to __main__.py" />
|
|
160
|
+
<MESSAGE value="README work" />
|
|
161
|
+
<MESSAGE value="README work - I'm sorry, I needed to test the gif rendering" />
|
|
162
|
+
<option name="LAST_COMMIT_MESSAGE" value="README work - I'm sorry, I needed to test the gif rendering" />
|
|
163
|
+
</component>
|
|
164
|
+
</project>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 theobaldsuperuser
|
|
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,82 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: penguin-agent-package
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Simple, minimalistic AI-Agent for Linux that runs on almost every device
|
|
5
|
+
Author-email: theobalduser <fzierenberg@icloud.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Requires-Python: >=3.12
|
|
11
|
+
Requires-Dist: animation>=0.0.7
|
|
12
|
+
Requires-Dist: annotated-types>=0.8.0
|
|
13
|
+
Requires-Dist: anyio>=4.15.1
|
|
14
|
+
Requires-Dist: certifi>=2026.7.22
|
|
15
|
+
Requires-Dist: dotenv>=1.2.4
|
|
16
|
+
Requires-Dist: ghostprint>=2.0.0
|
|
17
|
+
Requires-Dist: h11>=0.16.0
|
|
18
|
+
Requires-Dist: httpcore>=1.0.9
|
|
19
|
+
Requires-Dist: httpx>=0.28.1
|
|
20
|
+
Requires-Dist: idna>=3.20
|
|
21
|
+
Requires-Dist: ollama>=0.6.2
|
|
22
|
+
Requires-Dist: prompt-toolkit>=3.0.53
|
|
23
|
+
Requires-Dist: pychalk>=2.0.1
|
|
24
|
+
Requires-Dist: pydantic-core>=2.46.5
|
|
25
|
+
Requires-Dist: pydantic>=2.13.5
|
|
26
|
+
Requires-Dist: six>=1.17.0
|
|
27
|
+
Requires-Dist: typing-extensions>=4.16.0
|
|
28
|
+
Requires-Dist: typing-inspection>=0.4.4
|
|
29
|
+
Requires-Dist: wcwidth>=0.9.1
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# penguin
|
|
33
|
+
Simple, minimalist AI-Agent for Linux that runs on almost every device
|
|
34
|
+
|
|
35
|
+
.gif)
|
|
36
|
+
|
|
37
|
+
## LLM
|
|
38
|
+
Penguin uses (at the moment) the qwen3.5:2b model with Ollama as inference. The model can run at a good conversation speed on most modern laptops and a bit slower on older machines, the minimum I reached with an eight years old Lenovo Thinkpad(i5, 7th generation?) was 7 t/s. I recommend using an 32k context window, which is enough for most applications you will run with penguin.
|
|
39
|
+
|
|
40
|
+
## Installing & Quick Start
|
|
41
|
+
### One-line install command using pipx:
|
|
42
|
+
[pipx](https://github.com/pypa/pipx) is a modern PyPi Installer for CLI applications - like apt but for python. Use it to install the ```penguin-agent-package``` with a systemwide command ```penguin``` :
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
sudo pipx install --global --program-name penguin penguin-agent-package
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Then type ```penguin``` in your terminal
|
|
49
|
+
### Standard install using pip:
|
|
50
|
+
[pip](https://github.com/pypa/pip) is the standard PyPi Installer. Install ```penguin-agent-package``` using pip in a virtual environment:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
pip install penguin-agent-package
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
>[!WARNING]
|
|
58
|
+
>This will not install the systemwide ```penguin``` command. You need to add it manually to PATH.
|
|
59
|
+
|
|
60
|
+
## Features
|
|
61
|
+
Penguin is a very, very simple tool:
|
|
62
|
+
+ Uses an Ollama backend(see [LLM](#llm))
|
|
63
|
+
+ Three basic tools in the agent-loop:
|
|
64
|
+
* execute_bash_command
|
|
65
|
+
* get_memory_entry
|
|
66
|
+
* enter_memory_entry
|
|
67
|
+
+ Executes bash commands always at ```~```
|
|
68
|
+
+ Uses [prompt_toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit) to manage sessions with history and suggestions
|
|
69
|
+
+ Uses memory.json file for managing the agent memory
|
|
70
|
+
|
|
71
|
+
## Run it
|
|
72
|
+
Just type:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
penguin
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## How it works
|
|
79
|
+
The choice of Ollama is due entirely to its good tool-calling integration in python. Tool schemes are converted by the ollama library to JSON and passed to the native function calling of the model.
|
|
80
|
+
|
|
81
|
+
## Credits
|
|
82
|
+
I heavily used the prompt_toolkit library for the design and the interaction with the tool. Thanks also to the amazing ollama python library that makes it so easy to use LLMs with Python.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# penguin
|
|
2
|
+
Simple, minimalist AI-Agent for Linux that runs on almost every device
|
|
3
|
+
|
|
4
|
+
.gif)
|
|
5
|
+
|
|
6
|
+
## LLM
|
|
7
|
+
Penguin uses (at the moment) the qwen3.5:2b model with Ollama as inference. The model can run at a good conversation speed on most modern laptops and a bit slower on older machines, the minimum I reached with an eight years old Lenovo Thinkpad(i5, 7th generation?) was 7 t/s. I recommend using an 32k context window, which is enough for most applications you will run with penguin.
|
|
8
|
+
|
|
9
|
+
## Installing & Quick Start
|
|
10
|
+
### One-line install command using pipx:
|
|
11
|
+
[pipx](https://github.com/pypa/pipx) is a modern PyPi Installer for CLI applications - like apt but for python. Use it to install the ```penguin-agent-package``` with a systemwide command ```penguin``` :
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
sudo pipx install --global --program-name penguin penguin-agent-package
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Then type ```penguin``` in your terminal
|
|
18
|
+
### Standard install using pip:
|
|
19
|
+
[pip](https://github.com/pypa/pip) is the standard PyPi Installer. Install ```penguin-agent-package``` using pip in a virtual environment:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
pip install penguin-agent-package
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
>[!WARNING]
|
|
27
|
+
>This will not install the systemwide ```penguin``` command. You need to add it manually to PATH.
|
|
28
|
+
|
|
29
|
+
## Features
|
|
30
|
+
Penguin is a very, very simple tool:
|
|
31
|
+
+ Uses an Ollama backend(see [LLM](#llm))
|
|
32
|
+
+ Three basic tools in the agent-loop:
|
|
33
|
+
* execute_bash_command
|
|
34
|
+
* get_memory_entry
|
|
35
|
+
* enter_memory_entry
|
|
36
|
+
+ Executes bash commands always at ```~```
|
|
37
|
+
+ Uses [prompt_toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit) to manage sessions with history and suggestions
|
|
38
|
+
+ Uses memory.json file for managing the agent memory
|
|
39
|
+
|
|
40
|
+
## Run it
|
|
41
|
+
Just type:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
penguin
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## How it works
|
|
48
|
+
The choice of Ollama is due entirely to its good tool-calling integration in python. Tool schemes are converted by the ollama library to JSON and passed to the native function calling of the model.
|
|
49
|
+
|
|
50
|
+
## Credits
|
|
51
|
+
I heavily used the prompt_toolkit library for the design and the interaction with the tool. Thanks also to the amazing ollama python library that makes it so easy to use LLMs with Python.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
[tool.hatch.build.targets.wheel]
|
|
5
|
+
packages = ["src/penguin"]
|
|
6
|
+
[project]
|
|
7
|
+
name = "penguin-agent-package"
|
|
8
|
+
version = "0.1.0"
|
|
9
|
+
description = "Simple, minimalistic AI-Agent for Linux that runs on almost every device"
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
requires-python = ">=3.12"
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Programming Language :: Python :: 3",
|
|
14
|
+
"Operating System :: OS Independent",
|
|
15
|
+
]
|
|
16
|
+
license = "MIT"
|
|
17
|
+
authors = [
|
|
18
|
+
{ name="theobalduser", email="fzierenberg@icloud.com" },
|
|
19
|
+
]
|
|
20
|
+
dependencies = [
|
|
21
|
+
"animation>=0.0.7",
|
|
22
|
+
"annotated-types>=0.8.0",
|
|
23
|
+
"anyio>=4.15.1",
|
|
24
|
+
"certifi>=2026.7.22",
|
|
25
|
+
"ghostprint>=2.0.0",
|
|
26
|
+
"h11>=0.16.0",
|
|
27
|
+
"httpcore>=1.0.9",
|
|
28
|
+
"httpx>=0.28.1",
|
|
29
|
+
"idna>=3.20",
|
|
30
|
+
"ollama>=0.6.2",
|
|
31
|
+
"prompt_toolkit>=3.0.53",
|
|
32
|
+
"pychalk>=2.0.1",
|
|
33
|
+
"pydantic>=2.13.5",
|
|
34
|
+
"pydantic_core>=2.46.5",
|
|
35
|
+
"six>=1.17.0",
|
|
36
|
+
"typing-inspection>=0.4.4",
|
|
37
|
+
"typing_extensions>=4.16.0",
|
|
38
|
+
"wcwidth>=0.9.1",
|
|
39
|
+
"dotenv>=1.2.4"
|
|
40
|
+
]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
animation>=0.0.7
|
|
2
|
+
annotated-types>=0.8.0
|
|
3
|
+
anyio>=4.15.1
|
|
4
|
+
certifi>=2026.7.22
|
|
5
|
+
ghostprint>=2.0.0
|
|
6
|
+
h11>=0.16.0
|
|
7
|
+
httpcore>=1.0.9
|
|
8
|
+
httpx>=0.28.1
|
|
9
|
+
idna>=3.20
|
|
10
|
+
ollama>=0.6.2
|
|
11
|
+
prompt_toolkit>=3.0.53
|
|
12
|
+
pychalk>=2.0.1
|
|
13
|
+
pydantic>=2.13.5
|
|
14
|
+
pydantic_core>=2.46.5
|
|
15
|
+
six>=1.17.0
|
|
16
|
+
typing-inspection>=0.4.4
|
|
17
|
+
typing_extensions>=4.16.0
|
|
18
|
+
wcwidth>=0.9.1
|
|
19
|
+
|
|
20
|
+
python-dotenv
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
from ollama import Client
|
|
2
|
+
from dotenv import load_dotenv
|
|
3
|
+
from prompt_toolkit import print_formatted_text
|
|
4
|
+
from prompt_toolkit.completion import WordCompleter
|
|
5
|
+
from prompt_toolkit.formatted_text import *
|
|
6
|
+
from prompt_toolkit.shortcuts import PromptSession
|
|
7
|
+
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
|
|
8
|
+
from prompt_toolkit.history import FileHistory
|
|
9
|
+
from prompt_toolkit.styles import Style
|
|
10
|
+
from ghostprint import typewrite
|
|
11
|
+
import animation
|
|
12
|
+
import subprocess
|
|
13
|
+
import json
|
|
14
|
+
import datetime
|
|
15
|
+
import os
|
|
16
|
+
|
|
17
|
+
load_dotenv()
|
|
18
|
+
print(os.getenv("OLLAMA_BASE_URL"))
|
|
19
|
+
if os.getenv('OLLAMA_BASE_URL') is None:
|
|
20
|
+
print('No OLLAMA_BASE_URL found.')
|
|
21
|
+
baseurl = input('Please enter your ollama base URL: ')
|
|
22
|
+
with open('.env', 'w') as file:
|
|
23
|
+
file.write('OLLAMA_BASE_URL=' + baseurl)
|
|
24
|
+
load_dotenv()
|
|
25
|
+
ollamaclient = Client(host=os.getenv('OLLAMA_BASE_URL'))
|
|
26
|
+
|
|
27
|
+
spinner = animation.Wait('spinner', text='penguin is working')
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def execute_bash_command(command: str) -> str:
|
|
31
|
+
"""Execute bash commands in a linux shell environnemnt. Give command as full string.
|
|
32
|
+
Args:
|
|
33
|
+
command -> str
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
spinner.stop()
|
|
37
|
+
print("Using bash command: ")
|
|
38
|
+
try:
|
|
39
|
+
result = subprocess.run(command, check=True, cwd="/home/ferdinand", shell=True, capture_output=True, text=True)
|
|
40
|
+
try:
|
|
41
|
+
print_formatted_text(HTML(f'<violet>{str(command)}</violet>'))
|
|
42
|
+
print_formatted_text(HTML(f'<seagreen>{str(result.stdout.strip())}</seagreen>'))
|
|
43
|
+
except:
|
|
44
|
+
print(command)
|
|
45
|
+
print(result.stdout.strip())
|
|
46
|
+
|
|
47
|
+
spinner.start()
|
|
48
|
+
return result.stdout.strip()
|
|
49
|
+
except subprocess.CalledProcessError as e:
|
|
50
|
+
print(command)
|
|
51
|
+
print('error')
|
|
52
|
+
spinner.start()
|
|
53
|
+
return e.stderr.strip()
|
|
54
|
+
|
|
55
|
+
def get_memory_entry(keyword: str) -> str:
|
|
56
|
+
"""Get memory entries by keyword. The tool will return all the memory entries matching the keyword. Use this tool when searching for a solution for problem you already solved.
|
|
57
|
+
Args:
|
|
58
|
+
keyword -> str
|
|
59
|
+
"""
|
|
60
|
+
spinner.stop()
|
|
61
|
+
print("Using get_memory_entry()")
|
|
62
|
+
print(keyword)
|
|
63
|
+
spinner.start()
|
|
64
|
+
matching_entries = []
|
|
65
|
+
with open("memory.json", "r+") as file:
|
|
66
|
+
data = json.load(file)
|
|
67
|
+
for entry in data["memory_entries"]:
|
|
68
|
+
if len(set(keyword.split()).intersection(set(entry["keyword"].split()))) != 0 or len(set(keyword.split()).intersection(set(entry["entry"].split()))) != 0:
|
|
69
|
+
matching_entries.append(entry)
|
|
70
|
+
file.seek(0)
|
|
71
|
+
json.dump(data, file, indent=4)
|
|
72
|
+
return str(matching_entries)
|
|
73
|
+
|
|
74
|
+
def enter_memory_entry(keyword: str, entry: str) -> str:
|
|
75
|
+
"""Enter memory entry with keyword and entry. The tool will enter your memory entry. Use when solved a relevant problem to remember the solution. The keyword is to refind it later, so keep it clear. You may give your entry in the entry argument.
|
|
76
|
+
Args:
|
|
77
|
+
keyword -> str
|
|
78
|
+
entry -> str
|
|
79
|
+
"""
|
|
80
|
+
spinner.stop()
|
|
81
|
+
print("Using enter_memory_entry()")
|
|
82
|
+
print(keyword, entry)
|
|
83
|
+
spinner.start()
|
|
84
|
+
entryjson = {"timestamp": datetime.datetime.now(), "keyword": keyword, "entry": entry}
|
|
85
|
+
try:
|
|
86
|
+
with open("memory.json", "r", encoding="utf-8") as file:
|
|
87
|
+
data = json.load(file)
|
|
88
|
+
except (FileNotFoundError, json.JSONDecodeError):
|
|
89
|
+
data = {"memory_entries": []}
|
|
90
|
+
data["memory_entries"].append(entryjson)
|
|
91
|
+
with open("memory.json.tmp", "w", encoding="utf-8") as temp_file:
|
|
92
|
+
json.dump(data, temp_file, indent=4, ensure_ascii=False, default=str)
|
|
93
|
+
os.replace("memory.json.tmp", "memory.json")
|
|
94
|
+
print("Successfully updated file safely!")
|
|
95
|
+
|
|
96
|
+
return str([keyword, entry])
|
|
97
|
+
|
|
98
|
+
def bottom_toolbar():
|
|
99
|
+
return HTML('This may not work... <b><style bg="ansired">DO IT YOURSELF</style></b>!')
|
|
100
|
+
|
|
101
|
+
list_of_tools = ["execute_bash_command", "get_memory_entry", "enter_memory_entry"]
|
|
102
|
+
tool_dict = {"execute_bash_command": execute_bash_command, "enter_memory_entry": enter_memory_entry, "get_memory_entry": get_memory_entry}
|
|
103
|
+
messages_memory = [{'role': 'system', 'content': "You are a linux ai-assistant called penguin. You can execute bash commands and use your memory to help the user completiting tasks. ALWAYS provide a final answer WITHOUT tool calls to inform he user baout your results."}]
|
|
104
|
+
messages = []
|
|
105
|
+
|
|
106
|
+
session = PromptSession(history=FileHistory(".history"), bottom_toolbar=bottom_toolbar)
|
|
107
|
+
def run_agent(message, maximal_num_turns):
|
|
108
|
+
global messages_memory
|
|
109
|
+
global messages
|
|
110
|
+
messages = []
|
|
111
|
+
messages_memory.append({'role': 'user', 'content': message})
|
|
112
|
+
messages = messages_memory
|
|
113
|
+
spinner.start()
|
|
114
|
+
for turn_number in range(maximal_num_turns):
|
|
115
|
+
response = ollamaclient.chat(model="qwen3.5:2b_context", messages=messages, tools=[execute_bash_command, get_memory_entry, enter_memory_entry])
|
|
116
|
+
|
|
117
|
+
message_out = response["message"]
|
|
118
|
+
|
|
119
|
+
messages.append(message_out)
|
|
120
|
+
messages_memory.append(message_out)
|
|
121
|
+
tool_calls = message_out.get("tool_calls") or []
|
|
122
|
+
if not tool_calls:
|
|
123
|
+
spinner.stop()
|
|
124
|
+
return message_out["content"]
|
|
125
|
+
for call in tool_calls:
|
|
126
|
+
function_name = call["function"]["name"]
|
|
127
|
+
function_args_list = call["function"]["arguments"]
|
|
128
|
+
if function_name not in list_of_tools:
|
|
129
|
+
spinner.stop()
|
|
130
|
+
result = json.dumps({'error': f'Unknown tool or function: {function_name}'})
|
|
131
|
+
else:
|
|
132
|
+
result = tool_dict[function_name](**function_args_list)
|
|
133
|
+
messages.append({'role': 'tool', 'name': function_name, 'content': result})
|
|
134
|
+
messages_memory.append({'role': 'tool', 'name': function_name, 'content': result})
|
|
135
|
+
|
|
136
|
+
spinner.stop()
|
|
137
|
+
|
|
138
|
+
return "No final answer: running out of turns."
|
|
139
|
+
|
|
140
|
+
style = Style.from_dict(
|
|
141
|
+
{
|
|
142
|
+
"frame.border": "#893333",
|
|
143
|
+
}
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def main():
|
|
148
|
+
os.system('cls' if os.name == 'nt' else 'clear')
|
|
149
|
+
completer = WordCompleter(['/exit'], ignore_case=True)
|
|
150
|
+
while True:
|
|
151
|
+
user_input = session.prompt(HTML('<ansired>penguin></ansired> '), completer=completer, show_frame=True, style=style, auto_suggest=AutoSuggestFromHistory(), enable_history_search=True)
|
|
152
|
+
if user_input == "/exit":
|
|
153
|
+
print('Exiting penguin.')
|
|
154
|
+
break
|
|
155
|
+
else:
|
|
156
|
+
typewrite(run_agent(user_input, 30), speed=0.01)
|
|
157
|
+
|
|
158
|
+
if __name__ == "__main__":
|
|
159
|
+
main()
|