linux-assistant 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4 @@
1
+ # linux_assistant/__init__.py
2
+
3
+ __version__ = "0.1.0"
4
+
@@ -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()
linux_assistant/app.py ADDED
@@ -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,3 @@
1
+ MAX_SEARCH_RESULTS = 5
2
+ WIKIPEDIA_RESULTS = 1
3
+ SHOW_CODE_OUTPUT = False
@@ -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,8 @@
1
+ def require_llama():
2
+ try:
3
+ import llama_cpp
4
+ except ImportError:
5
+ raise RuntimeError(
6
+ "llama-cpp-python not installed.\n"
7
+ "Run: linux-assistant setup"
8
+ )
@@ -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,19 @@
1
+ linux_assistant/__init__.py,sha256=dlMJR8_PnItr5R7CuL6GqHzgmD8szQINN-qv3RYqlyc,54
2
+ linux_assistant/__main__.py,sha256=GkLWH6yYSp-EPjJoMWO9FhYuzsBRaGbAmAsI5S19WNI,514
3
+ linux_assistant/app.py,sha256=EDIIMWKVgurCG4bwXcxxUN5Xib4yh2wltbsvRJTRh04,585
4
+ linux_assistant/setup.py,sha256=AzKkdr7bDOTT2h_pmFBwquoulZS-Ag-I6G6V5AORluc,1497
5
+ linux_assistant/graph/config.py,sha256=XSSBf9jULfzBkaGnNdCbN5JJFGmH8k3gBBbE8x4bVjk,69
6
+ linux_assistant/graph/graph.py,sha256=09aZmwFt2SOjirUMRQOjGUnireSHYi2r_gt_nUSm1vM,1370
7
+ linux_assistant/graph/nodes.py,sha256=PInjFEz6ugvBy-4FGWRg8NwH3fCJ5kBc2-KPszBmcsg,4380
8
+ linux_assistant/models/config.py,sha256=D707fJ26Y1H5d2scrmoDLHgVa4jV9gdgHymBZpG3QAo,1869
9
+ linux_assistant/models/model_nodes.py,sha256=HSITPDaz_z6ItBDfCobq9_Cor162rHsuXkzIY-205YQ,2866
10
+ linux_assistant/runtime/checks.py,sha256=LlzA4asFdR6vM_DfzzfkBCTYj-ml-P4arZIbTWTlUOE,205
11
+ linux_assistant/utils/console_utils.py,sha256=amE0y1D0SzKU3BkAECQois8BPxcHW65x3ijubmgdKLM,942
12
+ linux_assistant/utils/dicts.py,sha256=zC3QePUf1cVIvmooV3vhbIPFfE46iHdWrO3REZrtHcw,348
13
+ linux_assistant/utils/subprocess.py,sha256=CCKwGKOwU89s2c--M2l5nbRsApRUD0pVHXsDs95r0l4,2634
14
+ linux_assistant-0.1.0.dist-info/licenses/LICENSE,sha256=00-Y0nUL-zWri7CsjEtbogU9Na1DRmrEg_BdH_wViK0,1156
15
+ linux_assistant-0.1.0.dist-info/METADATA,sha256=p0Id3V1VUKVsxaNFJpBkrNSM21H-6SrycJKwWBC43Nw,4183
16
+ linux_assistant-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
17
+ linux_assistant-0.1.0.dist-info/entry_points.txt,sha256=28iY1-J8GqNW54RY3MmCQ1aj3A_aa0Domf0ruQUxuuw,66
18
+ linux_assistant-0.1.0.dist-info/top_level.txt,sha256=ClGTbRQlqUYxbbL_9Y_cM4nAAObdue-YdB8TbACfZeo,16
19
+ linux_assistant-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ linux-assistant = linux_assistant.__main__:main
@@ -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 @@
1
+ linux_assistant