linux-assistant 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.
- linux_assistant-0.1.0/LICENSE +21 -0
- linux_assistant-0.1.0/PKG-INFO +128 -0
- linux_assistant-0.1.0/README.md +95 -0
- linux_assistant-0.1.0/pyproject.toml +22 -0
- linux_assistant-0.1.0/setup.cfg +4 -0
- linux_assistant-0.1.0/src/linux_assistant/__init__.py +4 -0
- linux_assistant-0.1.0/src/linux_assistant/__main__.py +33 -0
- linux_assistant-0.1.0/src/linux_assistant/app.py +25 -0
- linux_assistant-0.1.0/src/linux_assistant/graph/config.py +3 -0
- linux_assistant-0.1.0/src/linux_assistant/graph/graph.py +30 -0
- linux_assistant-0.1.0/src/linux_assistant/graph/nodes.py +110 -0
- linux_assistant-0.1.0/src/linux_assistant/models/config.py +23 -0
- linux_assistant-0.1.0/src/linux_assistant/models/model_nodes.py +69 -0
- linux_assistant-0.1.0/src/linux_assistant/runtime/checks.py +8 -0
- linux_assistant-0.1.0/src/linux_assistant/setup.py +67 -0
- linux_assistant-0.1.0/src/linux_assistant/utils/console_utils.py +27 -0
- linux_assistant-0.1.0/src/linux_assistant/utils/dicts.py +13 -0
- linux_assistant-0.1.0/src/linux_assistant/utils/subprocess.py +81 -0
- linux_assistant-0.1.0/src/linux_assistant.egg-info/PKG-INFO +128 -0
- linux_assistant-0.1.0/src/linux_assistant.egg-info/SOURCES.txt +22 -0
- linux_assistant-0.1.0/src/linux_assistant.egg-info/dependency_links.txt +1 -0
- linux_assistant-0.1.0/src/linux_assistant.egg-info/entry_points.txt +2 -0
- linux_assistant-0.1.0/src/linux_assistant.egg-info/requires.txt +2 -0
- linux_assistant-0.1.0/src/linux_assistant.egg-info/top_level.txt +2 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Ali Fathi Jahromi
|
|
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
|
|
13
|
+
all 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
|
|
21
|
+
THE SOFTWARE.
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: linux-assistant
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: An AI-powered natural-language Linux assistant
|
|
5
|
+
Author-email: Ali Fathi Jahromi <alifathijahromi@gmail.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2025 Ali Fathi Jahromi
|
|
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
|
|
18
|
+
all 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
|
|
26
|
+
THE SOFTWARE.
|
|
27
|
+
Requires-Python: >=3.9
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Requires-Dist: langchain
|
|
31
|
+
Requires-Dist: click
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
# linux‑Assistant
|
|
35
|
+
|
|
36
|
+
An AI-powered assistant that lets you interact with your Linux system using natural language. Ask it to list files, install packages, inspect processes, or perform any shell task—behind the scenes it translates your request into safe bash commands and runs them for you.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Features
|
|
41
|
+
|
|
42
|
+
- **Natural‑language interface**: Describe what you want to do—no need to remember exact commands.
|
|
43
|
+
- **Interactive shell execution**: The assistant decides when to run commands, executes them in a temporary script, and returns the output.
|
|
44
|
+
- **Continuous conversation**: You can follow up on results, ask for clarifications, or chain multiple operations in one session.
|
|
45
|
+
- **Customizable**: Swap in any Ollama‑compatible model and adjust prompts to suit your workflow.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Prerequisites
|
|
50
|
+
|
|
51
|
+
- **Linux** with Bash installed
|
|
52
|
+
- **Python 3.12+**
|
|
53
|
+
- [Ollama CLI & daemon](https://ollama.com/) with at least one local model (e.g. `qwen3:8b`)
|
|
54
|
+
- (Optional but recommended) A dedicated Python virtual environment
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Installation
|
|
59
|
+
|
|
60
|
+
1. **Clone the repository**
|
|
61
|
+
```bash
|
|
62
|
+
git clone https://github.com/alifthi/linux-Assistant.git
|
|
63
|
+
cd linux-Assistant
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
2. **Set up a virtual environment**
|
|
67
|
+
```bash
|
|
68
|
+
python3 -m venv .venv
|
|
69
|
+
source .venv/bin/activate
|
|
70
|
+
```
|
|
71
|
+
3. **Install ollama and pull model**
|
|
72
|
+
```bash
|
|
73
|
+
curl -fsSL https://ollama.com/install.sh | sh
|
|
74
|
+
ollama pull qwen3:8B # Or any other models you want to use
|
|
75
|
+
```
|
|
76
|
+
4. **Install dependencies**
|
|
77
|
+
```bash
|
|
78
|
+
pip install -r requirements.txt
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Configuration
|
|
84
|
+
|
|
85
|
+
Open **`models/config.py`** and adjust:
|
|
86
|
+
|
|
87
|
+
- `MODEL_NAME` – the Ollama model you wish to use (e.g. `"qwen3:8b"`).
|
|
88
|
+
- `SYSTEM_PROMPT` – how the assistant should frame its replies and decide when to run shell commands.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Usage
|
|
93
|
+
|
|
94
|
+
Run the assistant and start chatting:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
cd src
|
|
98
|
+
python -m linux_assistant
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
- Ask anything you would normally do in the terminal.
|
|
102
|
+
|
|
103
|
+
- Continue the conversation naturally or type `exit` to quit.
|
|
104
|
+
---
|
|
105
|
+
Run the assistant using Docker
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
docker run --name <Container's name> \
|
|
109
|
+
-v linux_assistant_volume:/app/data \
|
|
110
|
+
-v "$(pwd)":/app/workspace alifthi/linux-assistant
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Contributing
|
|
117
|
+
|
|
118
|
+
Contributions and feedback are welcome! Feel free to:
|
|
119
|
+
|
|
120
|
+
- Open issues for bugs or feature requests
|
|
121
|
+
- Submit pull requests to improve prompts, handling, or documentation
|
|
122
|
+
- Suggest new use cases or integrations
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## License
|
|
127
|
+
|
|
128
|
+
Distributed under the **MIT License**. See [LICENSE](LICENSE) for details.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# linux‑Assistant
|
|
2
|
+
|
|
3
|
+
An AI-powered assistant that lets you interact with your Linux system using natural language. Ask it to list files, install packages, inspect processes, or perform any shell task—behind the scenes it translates your request into safe bash commands and runs them for you.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Natural‑language interface**: Describe what you want to do—no need to remember exact commands.
|
|
10
|
+
- **Interactive shell execution**: The assistant decides when to run commands, executes them in a temporary script, and returns the output.
|
|
11
|
+
- **Continuous conversation**: You can follow up on results, ask for clarifications, or chain multiple operations in one session.
|
|
12
|
+
- **Customizable**: Swap in any Ollama‑compatible model and adjust prompts to suit your workflow.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Prerequisites
|
|
17
|
+
|
|
18
|
+
- **Linux** with Bash installed
|
|
19
|
+
- **Python 3.12+**
|
|
20
|
+
- [Ollama CLI & daemon](https://ollama.com/) with at least one local model (e.g. `qwen3:8b`)
|
|
21
|
+
- (Optional but recommended) A dedicated Python virtual environment
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
1. **Clone the repository**
|
|
28
|
+
```bash
|
|
29
|
+
git clone https://github.com/alifthi/linux-Assistant.git
|
|
30
|
+
cd linux-Assistant
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
2. **Set up a virtual environment**
|
|
34
|
+
```bash
|
|
35
|
+
python3 -m venv .venv
|
|
36
|
+
source .venv/bin/activate
|
|
37
|
+
```
|
|
38
|
+
3. **Install ollama and pull model**
|
|
39
|
+
```bash
|
|
40
|
+
curl -fsSL https://ollama.com/install.sh | sh
|
|
41
|
+
ollama pull qwen3:8B # Or any other models you want to use
|
|
42
|
+
```
|
|
43
|
+
4. **Install dependencies**
|
|
44
|
+
```bash
|
|
45
|
+
pip install -r requirements.txt
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Configuration
|
|
51
|
+
|
|
52
|
+
Open **`models/config.py`** and adjust:
|
|
53
|
+
|
|
54
|
+
- `MODEL_NAME` – the Ollama model you wish to use (e.g. `"qwen3:8b"`).
|
|
55
|
+
- `SYSTEM_PROMPT` – how the assistant should frame its replies and decide when to run shell commands.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Usage
|
|
60
|
+
|
|
61
|
+
Run the assistant and start chatting:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
cd src
|
|
65
|
+
python -m linux_assistant
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
- Ask anything you would normally do in the terminal.
|
|
69
|
+
|
|
70
|
+
- Continue the conversation naturally or type `exit` to quit.
|
|
71
|
+
---
|
|
72
|
+
Run the assistant using Docker
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
docker run --name <Container's name> \
|
|
76
|
+
-v linux_assistant_volume:/app/data \
|
|
77
|
+
-v "$(pwd)":/app/workspace alifthi/linux-assistant
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Contributing
|
|
84
|
+
|
|
85
|
+
Contributions and feedback are welcome! Feel free to:
|
|
86
|
+
|
|
87
|
+
- Open issues for bugs or feature requests
|
|
88
|
+
- Submit pull requests to improve prompts, handling, or documentation
|
|
89
|
+
- Suggest new use cases or integrations
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## License
|
|
94
|
+
|
|
95
|
+
Distributed under the **MIT License**. See [LICENSE](LICENSE) for details.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "linux-assistant"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "An AI-powered natural-language Linux assistant"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { file = "LICENSE" }
|
|
11
|
+
authors = [{ name = "Ali Fathi Jahromi", email = "alifathijahromi@gmail.com" }]
|
|
12
|
+
requires-python = ">=3.9"
|
|
13
|
+
dependencies = ["langchain", "click"]
|
|
14
|
+
|
|
15
|
+
[project.scripts]
|
|
16
|
+
linux-assistant = "linux_assistant.__main__:main"
|
|
17
|
+
|
|
18
|
+
[tool.setuptools]
|
|
19
|
+
package-dir = {"" = "src"}
|
|
20
|
+
|
|
21
|
+
[tool.setuptools.packages.find]
|
|
22
|
+
where = ["src"]
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
|
|
2
|
+
import click
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@click.group(invoke_without_command=True)
|
|
6
|
+
@click.pass_context
|
|
7
|
+
def cli(ctx):
|
|
8
|
+
"""Linux Assistant CLI"""
|
|
9
|
+
if ctx.invoked_subcommand is None:
|
|
10
|
+
ctx.invoke(run)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@cli.command()
|
|
14
|
+
def run():
|
|
15
|
+
from linux_assistant.runtime.checks import require_llama
|
|
16
|
+
require_llama()
|
|
17
|
+
|
|
18
|
+
from linux_assistant.app import run_app
|
|
19
|
+
run_app()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@cli.command()
|
|
23
|
+
def setup():
|
|
24
|
+
from linux_assistant.setup import setup_cmd
|
|
25
|
+
setup_cmd()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def main():
|
|
29
|
+
cli()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
if __name__ == "__main__":
|
|
33
|
+
main()
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from linux_assistant.graph.graph import build_graph
|
|
2
|
+
from linux_assistant.utils.dicts import AgentState
|
|
3
|
+
from linux_assistant.utils.console_utils import console_utils
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def run_app():
|
|
7
|
+
app = build_graph()
|
|
8
|
+
console = console_utils()
|
|
9
|
+
|
|
10
|
+
state: AgentState = {"messages": [], "logger": console}
|
|
11
|
+
|
|
12
|
+
console.release_banner()
|
|
13
|
+
|
|
14
|
+
while True:
|
|
15
|
+
input_text = console.get_user_input()
|
|
16
|
+
|
|
17
|
+
if input_text == "exit":
|
|
18
|
+
break
|
|
19
|
+
|
|
20
|
+
state["messages"].append({
|
|
21
|
+
"role": "user",
|
|
22
|
+
"content": input_text
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
state = app.invoke(state)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from linux_assistant.graph.nodes import shell_node, prepare_shell_code,\
|
|
2
|
+
tool_select, prepare_tool_prompt, prepare_search_query, search_tools
|
|
3
|
+
from linux_assistant.utils.dicts import AgentState
|
|
4
|
+
from linux_assistant.models.model_nodes import model_nodes
|
|
5
|
+
from langgraph.graph import StateGraph, START,END
|
|
6
|
+
|
|
7
|
+
def build_graph() -> StateGraph:
|
|
8
|
+
''' This function builds graph '''
|
|
9
|
+
call_model = model_nodes()
|
|
10
|
+
search = search_tools()
|
|
11
|
+
graph = StateGraph(AgentState)
|
|
12
|
+
graph.set_entry_point('call_model')
|
|
13
|
+
graph.add_node('call_model', call_model.call_model)
|
|
14
|
+
graph.add_node('prepare_tool_prompt', prepare_tool_prompt)
|
|
15
|
+
graph.add_node('prepare_shell_code',prepare_shell_code)
|
|
16
|
+
graph.add_node('shell_node', shell_node)
|
|
17
|
+
graph.add_conditional_edges(
|
|
18
|
+
"call_model",
|
|
19
|
+
tool_select,
|
|
20
|
+
{'shell_node': 'prepare_shell_code', "search_node": "prepare_search_query" ,"nothing": END}
|
|
21
|
+
)
|
|
22
|
+
graph.add_node('prepare_search_query', prepare_search_query)
|
|
23
|
+
graph.add_node('search_node', search.search_node)
|
|
24
|
+
graph.add_edge('prepare_search_query', 'search_node')
|
|
25
|
+
graph.add_edge('search_node', 'call_model')
|
|
26
|
+
graph.add_edge('prepare_shell_code', 'shell_node')
|
|
27
|
+
graph.add_edge('shell_node', 'prepare_tool_prompt')
|
|
28
|
+
graph.add_edge('prepare_tool_prompt','call_model')
|
|
29
|
+
app = graph.compile()
|
|
30
|
+
return app
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from linux_assistant.graph.config import MAX_SEARCH_RESULTS, WIKIPEDIA_RESULTS, SHOW_CODE_OUTPUT
|
|
2
|
+
from linux_assistant.utils.dicts import AgentState
|
|
3
|
+
from linux_assistant.utils.subprocess import processor
|
|
4
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
5
|
+
from langchain_community.tools.wikipedia.tool import WikipediaQueryRun
|
|
6
|
+
from langchain_community.utilities.wikipedia import WikipediaAPIWrapper
|
|
7
|
+
from ddgs import DDGS
|
|
8
|
+
import time
|
|
9
|
+
import gc
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def shell_node( state: AgentState)-> AgentState:
|
|
14
|
+
'''To run a shell code that generated with AI'''
|
|
15
|
+
state['logger'].print_text("🔧 Running shell command ...", color='blue', end='\n')
|
|
16
|
+
print("\n")
|
|
17
|
+
t = time.perf_counter()
|
|
18
|
+
|
|
19
|
+
proc = processor(show_output=SHOW_CODE_OUTPUT)
|
|
20
|
+
proc.subprocess(state['code'])
|
|
21
|
+
|
|
22
|
+
interval = time.perf_counter() - t
|
|
23
|
+
|
|
24
|
+
state['stdout'], state['stderr'], state['exit_code'] = proc.output['stdout'], proc.output['stderr'], proc.output['returncode']
|
|
25
|
+
if state['exit_code'] == 0:
|
|
26
|
+
state['logger'].print_text(f"✔️ Completed in {interval:.3f}s", color='green')
|
|
27
|
+
else:
|
|
28
|
+
state['logger'].print_text("❌ Exited with error...", color='red')
|
|
29
|
+
print('\n')
|
|
30
|
+
return state
|
|
31
|
+
|
|
32
|
+
def tool_select(state: AgentState) -> AgentState:
|
|
33
|
+
''' To decide witch tool is needed '''
|
|
34
|
+
last = state["messages"][-1]['content'].split('</think>')[-1]
|
|
35
|
+
if "shell_node" in last:
|
|
36
|
+
return "shell_node"
|
|
37
|
+
elif "search_node" in last:
|
|
38
|
+
return "search_node"
|
|
39
|
+
else:
|
|
40
|
+
return "nothing"
|
|
41
|
+
|
|
42
|
+
def prepare_shell_code(state:AgentState) -> AgentState:
|
|
43
|
+
''' To extract generated code '''
|
|
44
|
+
last = state["messages"][-1]['content']
|
|
45
|
+
last = last.split('shell_node')[-1]
|
|
46
|
+
last = last.split('```bash')[-1].split('```')[0]
|
|
47
|
+
state['code'] = last
|
|
48
|
+
return state
|
|
49
|
+
|
|
50
|
+
def prepare_tool_prompt( state: AgentState) -> AgentState:
|
|
51
|
+
'''Prepare the output of executed command for LM'''
|
|
52
|
+
content = (
|
|
53
|
+
"Here is the output of command you generated:\n"
|
|
54
|
+
f"stdout:\n{state['stdout']}\n"
|
|
55
|
+
f"stderr:\n{state['stderr']}\n"
|
|
56
|
+
f"exit_code: {state['exit_code']}"
|
|
57
|
+
)
|
|
58
|
+
state['messages'].append({'role':'user', 'content': content})
|
|
59
|
+
|
|
60
|
+
return state
|
|
61
|
+
def prepare_search_query(state: AgentState) -> AgentState:
|
|
62
|
+
'''Prepare the query for giving to search_node'''
|
|
63
|
+
last = state["messages"][-1]['content']
|
|
64
|
+
last = last.split('search_node')[-1]
|
|
65
|
+
last = last.split('```query')[-1].split('```')[0]
|
|
66
|
+
state['search_query'] = last
|
|
67
|
+
return state
|
|
68
|
+
class search_tools:
|
|
69
|
+
def __init__(self):
|
|
70
|
+
wiki_api = WikipediaAPIWrapper(
|
|
71
|
+
top_k_results=WIKIPEDIA_RESULTS,
|
|
72
|
+
lang="en",
|
|
73
|
+
doc_content_chars_max=2000
|
|
74
|
+
)
|
|
75
|
+
self.wiki_tool = WikipediaQueryRun(
|
|
76
|
+
api_wrapper=wiki_api,
|
|
77
|
+
description="Search Wikipedia for general knowledge",
|
|
78
|
+
verbose=False
|
|
79
|
+
)
|
|
80
|
+
def search_in_wiki(self, query):
|
|
81
|
+
answer = self.wiki_tool.run(query)
|
|
82
|
+
return answer
|
|
83
|
+
@staticmethod
|
|
84
|
+
def search_duckduckgo(query):
|
|
85
|
+
with DDGS() as ddgs:
|
|
86
|
+
results = ddgs.text(query, max_results = MAX_SEARCH_RESULTS)
|
|
87
|
+
return results
|
|
88
|
+
def search_node(self, state: AgentState)-> AgentState:
|
|
89
|
+
'''A node for search'''
|
|
90
|
+
state['logger'].print_text("🔍 Searching ...", color='blue', end='\n')
|
|
91
|
+
print("\n")
|
|
92
|
+
with Progress(
|
|
93
|
+
SpinnerColumn(),
|
|
94
|
+
TextColumn("[progress.description]{task.description}"),
|
|
95
|
+
transient=True,) as prog:
|
|
96
|
+
task = prog.add_task("Searching...", total=None)
|
|
97
|
+
t = time.perf_counter()
|
|
98
|
+
wiki_search_res = self.search_in_wiki(state['search_query'])
|
|
99
|
+
ddg_res = self.search_duckduckgo(state['search_query'])
|
|
100
|
+
interval = time.perf_counter() - t
|
|
101
|
+
state['messages'].append({'role':'user', 'content': wiki_search_res})
|
|
102
|
+
for i, res in enumerate(ddg_res):
|
|
103
|
+
state['messages'].append({'role':'user', 'content': res['body']})
|
|
104
|
+
if len(ddg_res) == 0:
|
|
105
|
+
state['logger'].print_text("❌ No result found ", color='red')
|
|
106
|
+
else:
|
|
107
|
+
state['logger'].print_text(f"🔍 Search is completed in {interval}s, {len(ddg_res)} results found", color='green')
|
|
108
|
+
print('\n')
|
|
109
|
+
gc.collect()
|
|
110
|
+
return state
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
SYSTEM_PROMPT = ("You are an AI assistant to help user to do the tasks that user asks with there linux os",
|
|
2
|
+
"If users asks for something that can handle with there linux at first you must generate `shell_node` token at the begining of generation",
|
|
3
|
+
"then you must generate a Code like follow",
|
|
4
|
+
"```bash \n<your code> \n```",
|
|
5
|
+
"Do not generate any fallback response when you generate a shell code.",
|
|
6
|
+
"Do not generate more than one code block in each round of response."
|
|
7
|
+
"After executing the provided code you will recive the result of execution, then if it was successfull you should generate a fallback response else you must run another code if it's possible",
|
|
8
|
+
"Every time user asked for doing something you must generate `shell_node` token before code.",
|
|
9
|
+
"You are able to run multiple code on users linux and see the results then run another code.",
|
|
10
|
+
"each time you want to generate code only one batch of code are allowed, so do not generate multiple codes in each round of generation",
|
|
11
|
+
"If it's not possible to run another code you mut tell user that it's not possible and why.",
|
|
12
|
+
"You are able to search in web for that you must only generate `search_node` token",
|
|
13
|
+
"After that you generate the search_node you must provide the search query like follow:",
|
|
14
|
+
"```query \n<your search query>\n'''",
|
|
15
|
+
"If you need to search about your code or search about what user asked use this tool."
|
|
16
|
+
"If user didn't ask for doing anything with there linux OS only chat normally.",
|
|
17
|
+
"Do not miss special tokens before code or query")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
GENERATION_MODEL='Qwen3.5-4B-Q6_K.gguf'
|
|
21
|
+
REPO_ID = "unsloth/Qwen3.5-4B-GGUF"
|
|
22
|
+
|
|
23
|
+
SHOW_THINKS = False
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from llama_cpp import Llama, LlamaRAMCache
|
|
2
|
+
from huggingface_hub import hf_hub_download
|
|
3
|
+
from linux_assistant.models.config import SYSTEM_PROMPT, SHOW_THINKS,\
|
|
4
|
+
REPO_ID, GENERATION_MODEL
|
|
5
|
+
from linux_assistant.utils.dicts import AgentState
|
|
6
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
7
|
+
|
|
8
|
+
class model_nodes:
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self.model = self.build_model()
|
|
11
|
+
self.cache = LlamaRAMCache(capacity_bytes=512 << 20)
|
|
12
|
+
self.model.set_cache(self.cache)
|
|
13
|
+
def call_model(self, state: AgentState) -> AgentState:
|
|
14
|
+
''' A node to call model '''
|
|
15
|
+
if len(state['messages']) == 1:
|
|
16
|
+
system_message = {'role':'system', 'content': SYSTEM_PROMPT}
|
|
17
|
+
state['messages'] = [system_message] + state['messages']
|
|
18
|
+
|
|
19
|
+
stream = self.model.create_chat_completion(
|
|
20
|
+
messages=state['messages'],
|
|
21
|
+
temperature=0.7,
|
|
22
|
+
top_p=0.95,
|
|
23
|
+
min_p=0.05,
|
|
24
|
+
top_k=40,
|
|
25
|
+
stream=True,
|
|
26
|
+
)
|
|
27
|
+
is_think_generated = False
|
|
28
|
+
with Progress(
|
|
29
|
+
SpinnerColumn(),
|
|
30
|
+
TextColumn("[progress.description]{task.description}"),
|
|
31
|
+
transient=not SHOW_THINKS,) as prog:
|
|
32
|
+
task = prog.add_task("Thinking...", total=None)
|
|
33
|
+
response_content = ""
|
|
34
|
+
for chunk in stream:
|
|
35
|
+
chunk = chunk["choices"][0]["delta"].get("content", "")
|
|
36
|
+
response_content += chunk
|
|
37
|
+
if SHOW_THINKS:
|
|
38
|
+
prog.update(task, description=response_content, end = '', flush = True)
|
|
39
|
+
if chunk == '</think>':
|
|
40
|
+
is_think_generated = True
|
|
41
|
+
break
|
|
42
|
+
tmp = True
|
|
43
|
+
dont_show = False
|
|
44
|
+
for chunk in stream:
|
|
45
|
+
chunk = chunk["choices"][0]["delta"].get("content", "")
|
|
46
|
+
response_content += chunk
|
|
47
|
+
if (is_think_generated and (chunk == 'shell' or chunk == 'search')) or dont_show:
|
|
48
|
+
dont_show = True
|
|
49
|
+
continue
|
|
50
|
+
if tmp:
|
|
51
|
+
if chunk == '\n\n':
|
|
52
|
+
continue
|
|
53
|
+
tmp = False
|
|
54
|
+
state['logger'].print_text(' ➜ ', color = 'yellow')
|
|
55
|
+
state['logger'].print_text(chunk, color='white')
|
|
56
|
+
print('\n')
|
|
57
|
+
state['messages'].append({'role':'assistant', 'content': response_content})
|
|
58
|
+
return state
|
|
59
|
+
@staticmethod
|
|
60
|
+
def build_model():
|
|
61
|
+
''' A function to define the LM '''
|
|
62
|
+
model_path = hf_hub_download(repo_id=REPO_ID, filename=GENERATION_MODEL, local_dir="./model")
|
|
63
|
+
return Llama(
|
|
64
|
+
model_path=model_path,
|
|
65
|
+
n_ctx=4096,
|
|
66
|
+
n_gpu_layers=10,
|
|
67
|
+
chat_format='qwen',
|
|
68
|
+
verbose=False,
|
|
69
|
+
)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import platform
|
|
2
|
+
import subprocess
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def detect_cuda():
|
|
7
|
+
try:
|
|
8
|
+
result = subprocess.run(
|
|
9
|
+
["nvidia-smi"],
|
|
10
|
+
stdout=subprocess.PIPE,
|
|
11
|
+
stderr=subprocess.PIPE,
|
|
12
|
+
text=True
|
|
13
|
+
)
|
|
14
|
+
return result.returncode == 0
|
|
15
|
+
except FileNotFoundError:
|
|
16
|
+
return False
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def install_llama_cpp(cuda=False):
|
|
20
|
+
if cuda:
|
|
21
|
+
print("Installing CUDA version of llama-cpp-python...")
|
|
22
|
+
subprocess.check_call([
|
|
23
|
+
sys.executable,
|
|
24
|
+
"-m",
|
|
25
|
+
"pip",
|
|
26
|
+
"install",
|
|
27
|
+
"llama-cpp-python",
|
|
28
|
+
"--extra-index-url",
|
|
29
|
+
"https://abetlen.github.io/llama-cpp-python/whl/cu124"
|
|
30
|
+
])
|
|
31
|
+
else:
|
|
32
|
+
print("Installing CPU version of llama-cpp-python...")
|
|
33
|
+
subprocess.check_call([
|
|
34
|
+
sys.executable,
|
|
35
|
+
"-m",
|
|
36
|
+
"pip",
|
|
37
|
+
"install",
|
|
38
|
+
"llama-cpp-python"
|
|
39
|
+
])
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def setup_cmd():
|
|
43
|
+
print("Linux Assistant Setup\n")
|
|
44
|
+
|
|
45
|
+
system = platform.system()
|
|
46
|
+
print(f"Detected OS: {system}")
|
|
47
|
+
|
|
48
|
+
cuda = detect_cuda()
|
|
49
|
+
print(f"NVIDIA GPU detected: {cuda}")
|
|
50
|
+
|
|
51
|
+
choice = None
|
|
52
|
+
|
|
53
|
+
if cuda:
|
|
54
|
+
print("\nSelect backend:")
|
|
55
|
+
print("1) CUDA (recommended)")
|
|
56
|
+
print("2) CPU only")
|
|
57
|
+
|
|
58
|
+
choice = input("> ").strip()
|
|
59
|
+
|
|
60
|
+
use_cuda = (choice == "1")
|
|
61
|
+
else:
|
|
62
|
+
print("No GPU detected → using CPU")
|
|
63
|
+
use_cuda = False
|
|
64
|
+
|
|
65
|
+
install_llama_cpp(cuda=use_cuda)
|
|
66
|
+
|
|
67
|
+
print("\nSetup complete ✔")
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from pyfiglet import Figlet
|
|
2
|
+
from rich.console import Console
|
|
3
|
+
from rich.markdown import Markdown
|
|
4
|
+
from prompt_toolkit.styles import Style
|
|
5
|
+
import questionary
|
|
6
|
+
|
|
7
|
+
class console_utils:
|
|
8
|
+
def __init__(self):
|
|
9
|
+
self.console = Console()
|
|
10
|
+
self.banner = Figlet(font="slant").renderText("Linux Assistant")
|
|
11
|
+
self.intro_md = Markdown("Welcome! Enjoy your linux more.")
|
|
12
|
+
self.custom_style = Style.from_dict({
|
|
13
|
+
'question': 'magenta',
|
|
14
|
+
'answer': 'gray',
|
|
15
|
+
'pointer': 'yellow'})
|
|
16
|
+
def release_banner(self):
|
|
17
|
+
self.console.print(self.banner, style="cyan")
|
|
18
|
+
self.console.print(self.intro_md)
|
|
19
|
+
|
|
20
|
+
def get_user_input(self):
|
|
21
|
+
cmd = questionary.text("➜",style=self.custom_style,qmark="").ask()
|
|
22
|
+
if cmd == None:
|
|
23
|
+
raise SystemExit
|
|
24
|
+
return cmd
|
|
25
|
+
|
|
26
|
+
def print_text(self, text, color, end = ''):
|
|
27
|
+
self.console.print(f"[{color}]{text}", end=end)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from typing import TypedDict, Sequence
|
|
2
|
+
from linux_assistant.utils.console_utils import console_utils
|
|
3
|
+
from langchain_core.messages import BaseMessage
|
|
4
|
+
|
|
5
|
+
class AgentState(TypedDict):
|
|
6
|
+
messages: Sequence[dict]
|
|
7
|
+
logger: console_utils
|
|
8
|
+
search_query: str
|
|
9
|
+
code: str
|
|
10
|
+
search_query: str
|
|
11
|
+
stdout: str
|
|
12
|
+
stderr: str
|
|
13
|
+
exit_code: int
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import tempfile
|
|
3
|
+
import io, os
|
|
4
|
+
import threading
|
|
5
|
+
|
|
6
|
+
class processor:
|
|
7
|
+
def __init__(self, show_output=False):
|
|
8
|
+
self.output = {'stderr':None, "stdout":None, "returncode":None}
|
|
9
|
+
self.show_output = show_output
|
|
10
|
+
|
|
11
|
+
def subprocess(self, code):
|
|
12
|
+
'''Runing subprocess'''
|
|
13
|
+
path = self.create_tmp_file(code)
|
|
14
|
+
returncode, stdout, stderr = self.run_interactive_subprocess(['bash', path])
|
|
15
|
+
os.remove(path)
|
|
16
|
+
self.output = {'stderr':stderr, "stdout":stdout, "returncode":returncode}
|
|
17
|
+
|
|
18
|
+
def run_interactive_subprocess(self, command):
|
|
19
|
+
'''To run shell intractivly'''
|
|
20
|
+
stdout_buffer = io.StringIO()
|
|
21
|
+
stderr_buffer = io.StringIO()
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
process = subprocess.Popen(
|
|
25
|
+
command,
|
|
26
|
+
stdin=subprocess.PIPE,
|
|
27
|
+
stdout=subprocess.PIPE,
|
|
28
|
+
stderr=subprocess.PIPE,
|
|
29
|
+
text=True,
|
|
30
|
+
bufsize=1
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
stdout_thread = threading.Thread(
|
|
34
|
+
target=self.read_output,
|
|
35
|
+
args=(process.stdout, "Subprocess output", stdout_buffer, self.show_output)
|
|
36
|
+
)
|
|
37
|
+
stderr_thread = threading.Thread(
|
|
38
|
+
target=self.read_output,
|
|
39
|
+
args=(process.stderr, "Subprocess error", stderr_buffer, self.show_output)
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
stdout_thread.daemon = True
|
|
43
|
+
stderr_thread.daemon = True
|
|
44
|
+
|
|
45
|
+
stdout_thread.start()
|
|
46
|
+
stderr_thread.start()
|
|
47
|
+
|
|
48
|
+
process.stdin.close()
|
|
49
|
+
|
|
50
|
+
returncode = process.wait()
|
|
51
|
+
|
|
52
|
+
stdout_thread.join(timeout=1)
|
|
53
|
+
stderr_thread.join(timeout=1)
|
|
54
|
+
|
|
55
|
+
stdout_content = stdout_buffer.getvalue()
|
|
56
|
+
stderr_content = stderr_buffer.getvalue()
|
|
57
|
+
|
|
58
|
+
return returncode, stdout_content, stderr_content
|
|
59
|
+
|
|
60
|
+
except Exception as e:
|
|
61
|
+
print(f"Error: {e}")
|
|
62
|
+
return -1, "", str(e)
|
|
63
|
+
|
|
64
|
+
@staticmethod
|
|
65
|
+
def create_tmp_file(code):
|
|
66
|
+
'''Creating a temp file'''
|
|
67
|
+
with tempfile.NamedTemporaryFile("w", suffix=".sh", delete=False) as tf:
|
|
68
|
+
tf.write(code)
|
|
69
|
+
path = tf.name
|
|
70
|
+
return path
|
|
71
|
+
|
|
72
|
+
@staticmethod
|
|
73
|
+
def read_output(stream, prefix, buffer, show_output=True):
|
|
74
|
+
'''To gather outputs'''
|
|
75
|
+
while True:
|
|
76
|
+
line = stream.readline()
|
|
77
|
+
if not line:
|
|
78
|
+
break
|
|
79
|
+
if show_output:
|
|
80
|
+
print(f"{prefix}: {line.strip()}")
|
|
81
|
+
buffer.write(line)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: linux-assistant
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: An AI-powered natural-language Linux assistant
|
|
5
|
+
Author-email: Ali Fathi Jahromi <alifathijahromi@gmail.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2025 Ali Fathi Jahromi
|
|
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
|
|
18
|
+
all 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
|
|
26
|
+
THE SOFTWARE.
|
|
27
|
+
Requires-Python: >=3.9
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Requires-Dist: langchain
|
|
31
|
+
Requires-Dist: click
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
# linux‑Assistant
|
|
35
|
+
|
|
36
|
+
An AI-powered assistant that lets you interact with your Linux system using natural language. Ask it to list files, install packages, inspect processes, or perform any shell task—behind the scenes it translates your request into safe bash commands and runs them for you.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Features
|
|
41
|
+
|
|
42
|
+
- **Natural‑language interface**: Describe what you want to do—no need to remember exact commands.
|
|
43
|
+
- **Interactive shell execution**: The assistant decides when to run commands, executes them in a temporary script, and returns the output.
|
|
44
|
+
- **Continuous conversation**: You can follow up on results, ask for clarifications, or chain multiple operations in one session.
|
|
45
|
+
- **Customizable**: Swap in any Ollama‑compatible model and adjust prompts to suit your workflow.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Prerequisites
|
|
50
|
+
|
|
51
|
+
- **Linux** with Bash installed
|
|
52
|
+
- **Python 3.12+**
|
|
53
|
+
- [Ollama CLI & daemon](https://ollama.com/) with at least one local model (e.g. `qwen3:8b`)
|
|
54
|
+
- (Optional but recommended) A dedicated Python virtual environment
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Installation
|
|
59
|
+
|
|
60
|
+
1. **Clone the repository**
|
|
61
|
+
```bash
|
|
62
|
+
git clone https://github.com/alifthi/linux-Assistant.git
|
|
63
|
+
cd linux-Assistant
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
2. **Set up a virtual environment**
|
|
67
|
+
```bash
|
|
68
|
+
python3 -m venv .venv
|
|
69
|
+
source .venv/bin/activate
|
|
70
|
+
```
|
|
71
|
+
3. **Install ollama and pull model**
|
|
72
|
+
```bash
|
|
73
|
+
curl -fsSL https://ollama.com/install.sh | sh
|
|
74
|
+
ollama pull qwen3:8B # Or any other models you want to use
|
|
75
|
+
```
|
|
76
|
+
4. **Install dependencies**
|
|
77
|
+
```bash
|
|
78
|
+
pip install -r requirements.txt
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Configuration
|
|
84
|
+
|
|
85
|
+
Open **`models/config.py`** and adjust:
|
|
86
|
+
|
|
87
|
+
- `MODEL_NAME` – the Ollama model you wish to use (e.g. `"qwen3:8b"`).
|
|
88
|
+
- `SYSTEM_PROMPT` – how the assistant should frame its replies and decide when to run shell commands.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Usage
|
|
93
|
+
|
|
94
|
+
Run the assistant and start chatting:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
cd src
|
|
98
|
+
python -m linux_assistant
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
- Ask anything you would normally do in the terminal.
|
|
102
|
+
|
|
103
|
+
- Continue the conversation naturally or type `exit` to quit.
|
|
104
|
+
---
|
|
105
|
+
Run the assistant using Docker
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
docker run --name <Container's name> \
|
|
109
|
+
-v linux_assistant_volume:/app/data \
|
|
110
|
+
-v "$(pwd)":/app/workspace alifthi/linux-assistant
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Contributing
|
|
117
|
+
|
|
118
|
+
Contributions and feedback are welcome! Feel free to:
|
|
119
|
+
|
|
120
|
+
- Open issues for bugs or feature requests
|
|
121
|
+
- Submit pull requests to improve prompts, handling, or documentation
|
|
122
|
+
- Suggest new use cases or integrations
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## License
|
|
127
|
+
|
|
128
|
+
Distributed under the **MIT License**. See [LICENSE](LICENSE) for details.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/linux_assistant/__init__.py
|
|
5
|
+
src/linux_assistant/__main__.py
|
|
6
|
+
src/linux_assistant/app.py
|
|
7
|
+
src/linux_assistant/setup.py
|
|
8
|
+
src/linux_assistant.egg-info/PKG-INFO
|
|
9
|
+
src/linux_assistant.egg-info/SOURCES.txt
|
|
10
|
+
src/linux_assistant.egg-info/dependency_links.txt
|
|
11
|
+
src/linux_assistant.egg-info/entry_points.txt
|
|
12
|
+
src/linux_assistant.egg-info/requires.txt
|
|
13
|
+
src/linux_assistant.egg-info/top_level.txt
|
|
14
|
+
src/linux_assistant/graph/config.py
|
|
15
|
+
src/linux_assistant/graph/graph.py
|
|
16
|
+
src/linux_assistant/graph/nodes.py
|
|
17
|
+
src/linux_assistant/models/config.py
|
|
18
|
+
src/linux_assistant/models/model_nodes.py
|
|
19
|
+
src/linux_assistant/runtime/checks.py
|
|
20
|
+
src/linux_assistant/utils/console_utils.py
|
|
21
|
+
src/linux_assistant/utils/dicts.py
|
|
22
|
+
src/linux_assistant/utils/subprocess.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|