ape-linux 0.1.1__tar.gz → 0.2.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.
- {ape_linux-0.1.1 → ape_linux-0.2.0}/PKG-INFO +8 -8
- {ape_linux-0.1.1 → ape_linux-0.2.0}/README.md +7 -7
- ape_linux-0.2.0/ape.py +104 -0
- {ape_linux-0.1.1 → ape_linux-0.2.0}/pyproject.toml +5 -3
- ape_linux-0.1.1/src/ape/__init__.py +0 -1
- ape_linux-0.1.1/src/ape/cli.py +0 -61
- ape_linux-0.1.1/src/ape/llm.py +0 -76
- {ape_linux-0.1.1 → ape_linux-0.2.0}/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ape-linux
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: AI for Linux commands
|
|
5
5
|
Home-page: https://github.com/sbalian/ape
|
|
6
6
|
License: MIT
|
|
@@ -46,10 +46,10 @@ To install:
|
|
|
46
46
|
pipx install ape-linux
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
Next, set your API key:
|
|
49
|
+
Next, set your OpenAI API key:
|
|
50
50
|
|
|
51
51
|
```bash
|
|
52
|
-
export
|
|
52
|
+
export APE_OPENAI_API_KEY=key
|
|
53
53
|
```
|
|
54
54
|
|
|
55
55
|
To run:
|
|
@@ -76,19 +76,19 @@ Output:
|
|
|
76
76
|
find projects/ -type d -name ".venv" -exec rm -rf {} +
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
-
If you try to ask something unrelated to Linux commands
|
|
79
|
+
If you try to ask something unrelated to Linux commands:
|
|
80
80
|
|
|
81
81
|
```bash
|
|
82
82
|
ape "Tell me about monkeys"
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
you should get:
|
|
86
86
|
|
|
87
87
|
```text
|
|
88
|
-
Please
|
|
88
|
+
echo "Please try again."
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
-
You can change the model using `--model`. The default is `gpt-4o`.
|
|
91
|
+
You can change the model using `--model` or `-m`. The default is `gpt-4o`.
|
|
92
92
|
See [here](https://platform.openai.com/docs/models) for a list of models. For example:
|
|
93
93
|
|
|
94
94
|
```bash
|
|
@@ -101,7 +101,7 @@ Output:
|
|
|
101
101
|
ls -lha
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
-
If you pass `--execute`, the tool will run the command for you after printing it! Be careful with this as LLMs often make mistakes:
|
|
104
|
+
If you pass `--execute` or `-e`, the tool will run the command for you after printing it! Be careful with this as LLMs often make mistakes:
|
|
105
105
|
|
|
106
106
|
```bash
|
|
107
107
|
ape "Who am I logged in as?"
|
|
@@ -20,10 +20,10 @@ To install:
|
|
|
20
20
|
pipx install ape-linux
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
Next, set your API key:
|
|
23
|
+
Next, set your OpenAI API key:
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
|
-
export
|
|
26
|
+
export APE_OPENAI_API_KEY=key
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
To run:
|
|
@@ -50,19 +50,19 @@ Output:
|
|
|
50
50
|
find projects/ -type d -name ".venv" -exec rm -rf {} +
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
If you try to ask something unrelated to Linux commands
|
|
53
|
+
If you try to ask something unrelated to Linux commands:
|
|
54
54
|
|
|
55
55
|
```bash
|
|
56
56
|
ape "Tell me about monkeys"
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
you should get:
|
|
60
60
|
|
|
61
61
|
```text
|
|
62
|
-
Please
|
|
62
|
+
echo "Please try again."
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
-
You can change the model using `--model`. The default is `gpt-4o`.
|
|
65
|
+
You can change the model using `--model` or `-m`. The default is `gpt-4o`.
|
|
66
66
|
See [here](https://platform.openai.com/docs/models) for a list of models. For example:
|
|
67
67
|
|
|
68
68
|
```bash
|
|
@@ -75,7 +75,7 @@ Output:
|
|
|
75
75
|
ls -lha
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
-
If you pass `--execute`, the tool will run the command for you after printing it! Be careful with this as LLMs often make mistakes:
|
|
78
|
+
If you pass `--execute` or `-e`, the tool will run the command for you after printing it! Be careful with this as LLMs often make mistakes:
|
|
79
79
|
|
|
80
80
|
```bash
|
|
81
81
|
ape "Who am I logged in as?"
|
ape_linux-0.2.0/ape.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""AI for Linux commands."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
from typing import Annotated
|
|
6
|
+
|
|
7
|
+
import openai
|
|
8
|
+
import rich.console
|
|
9
|
+
import typer
|
|
10
|
+
|
|
11
|
+
app = typer.Typer(add_completion=False, pretty_exceptions_enable=False)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def call_llm(api_key: str, model: str, system_prompt: str, user_prompt: str):
|
|
15
|
+
return (
|
|
16
|
+
openai.OpenAI(api_key=api_key)
|
|
17
|
+
.chat.completions.create(
|
|
18
|
+
model=model,
|
|
19
|
+
messages=[
|
|
20
|
+
{"role": "system", "content": system_prompt},
|
|
21
|
+
{"role": "user", "content": user_prompt},
|
|
22
|
+
],
|
|
23
|
+
)
|
|
24
|
+
.choices[0]
|
|
25
|
+
.message.content
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@app.command()
|
|
30
|
+
def main(
|
|
31
|
+
query: Annotated[
|
|
32
|
+
str, typer.Argument(help="Query describing a Linux task.", show_default=False)
|
|
33
|
+
],
|
|
34
|
+
model: Annotated[
|
|
35
|
+
str,
|
|
36
|
+
typer.Option(
|
|
37
|
+
"--model",
|
|
38
|
+
"-m",
|
|
39
|
+
help="OpenAI model. See https://platform.openai.com/docs/models.",
|
|
40
|
+
),
|
|
41
|
+
] = "gpt-4o",
|
|
42
|
+
execute: Annotated[
|
|
43
|
+
bool,
|
|
44
|
+
typer.Option(
|
|
45
|
+
"--execute",
|
|
46
|
+
"-e",
|
|
47
|
+
help="Run the command if suggested. Dangerous!",
|
|
48
|
+
),
|
|
49
|
+
] = False,
|
|
50
|
+
):
|
|
51
|
+
"""Suggest a command for a Linux task described in QUERY.
|
|
52
|
+
|
|
53
|
+
Example: ape "Create a symbolic link named 'win' pointing to /mnt/c/Users/jdoe"
|
|
54
|
+
|
|
55
|
+
Output : ln -s /mnt/c/Users/jdoe win
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
console = rich.console.Console()
|
|
59
|
+
|
|
60
|
+
system_prompt = """\
|
|
61
|
+
You are a Linux command assistant. You will be asked a question about how to perform a task in Linux or Unix-like operating systems. You should only include in your answer the command or commands to perform the task. If you do not know how to perform the task, output "echo "Please try again."".
|
|
62
|
+
|
|
63
|
+
Here are a few examples.
|
|
64
|
+
|
|
65
|
+
Question: List all the files and directories in projects in my home directory
|
|
66
|
+
Answer: ls ~/projects
|
|
67
|
+
|
|
68
|
+
Question: What is my username?
|
|
69
|
+
Answer: whoami
|
|
70
|
+
|
|
71
|
+
Question: Find all files with the extension .txt under the current working directory
|
|
72
|
+
Answer: find . -name "*.txt"
|
|
73
|
+
|
|
74
|
+
Question: What is the captial of France?
|
|
75
|
+
Answer: echo "Please try again."
|
|
76
|
+
|
|
77
|
+
Question: Tell me a story
|
|
78
|
+
Answer: echo "Please try again.\"""" # noqa: E501
|
|
79
|
+
|
|
80
|
+
user_prompt = f"""\
|
|
81
|
+
Question: {query.strip()}
|
|
82
|
+
Answer:"""
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
api_key = os.environ["APE_OPENAI_API_KEY"]
|
|
86
|
+
except KeyError:
|
|
87
|
+
typer.echo("Set the environment variable APE_OPENAI_API_KEY.", err=True)
|
|
88
|
+
raise typer.Exit(1)
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
with console.status("[bold][blue]Processing ...", spinner="monkey"):
|
|
92
|
+
answer = call_llm(api_key, model, system_prompt, user_prompt)
|
|
93
|
+
if answer is None:
|
|
94
|
+
answer = 'echo "Please try again."'
|
|
95
|
+
typer.echo(answer)
|
|
96
|
+
if execute:
|
|
97
|
+
subprocess.check_call(answer, shell=True)
|
|
98
|
+
except openai.APIStatusError as error:
|
|
99
|
+
typer.echo(
|
|
100
|
+
f"OpenAI {error.status_code} error: "
|
|
101
|
+
f"{error.response.json()['error']['message']}",
|
|
102
|
+
err=True,
|
|
103
|
+
)
|
|
104
|
+
raise typer.Exit(1)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "ape-linux"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2.0"
|
|
4
4
|
description = "AI for Linux commands"
|
|
5
5
|
license = "MIT"
|
|
6
6
|
authors = ["Seto Balian <seto.balian@gmail.com>"]
|
|
@@ -16,7 +16,7 @@ classifiers = [
|
|
|
16
16
|
"License :: OSI Approved :: MIT License",
|
|
17
17
|
]
|
|
18
18
|
packages = [
|
|
19
|
-
{ include = "ape"
|
|
19
|
+
{ include = "ape.py" },
|
|
20
20
|
]
|
|
21
21
|
|
|
22
22
|
[tool.poetry.dependencies]
|
|
@@ -26,7 +26,7 @@ openai = "^1.30.3"
|
|
|
26
26
|
rich = "^13.7.1"
|
|
27
27
|
|
|
28
28
|
[tool.poetry.scripts]
|
|
29
|
-
ape = "ape
|
|
29
|
+
ape = "ape:app"
|
|
30
30
|
|
|
31
31
|
[tool.poetry.group.dev.dependencies]
|
|
32
32
|
ipython = "^8.24.0"
|
|
@@ -34,6 +34,8 @@ pytest = "^8.2.1"
|
|
|
34
34
|
ruff = "^0.4.5"
|
|
35
35
|
pyright = "^1.1.364"
|
|
36
36
|
|
|
37
|
+
[tool.pyright]
|
|
38
|
+
|
|
37
39
|
[build-system]
|
|
38
40
|
requires = ["poetry-core"]
|
|
39
41
|
build-backend = "poetry.core.masonry.api"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"""AI for Linux commands."""
|
ape_linux-0.1.1/src/ape/cli.py
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import subprocess
|
|
2
|
-
from typing import Annotated
|
|
3
|
-
|
|
4
|
-
import openai
|
|
5
|
-
import rich.console
|
|
6
|
-
import typer
|
|
7
|
-
|
|
8
|
-
from . import llm
|
|
9
|
-
|
|
10
|
-
app = typer.Typer(add_completion=False, pretty_exceptions_enable=False)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
@app.command()
|
|
14
|
-
def run(
|
|
15
|
-
query: Annotated[str, typer.Argument(help="Query describing a Linux task.")],
|
|
16
|
-
model: Annotated[
|
|
17
|
-
str,
|
|
18
|
-
typer.Option(
|
|
19
|
-
help="OpenAI model. See https://platform.openai.com/docs/models.",
|
|
20
|
-
),
|
|
21
|
-
] = "gpt-4o",
|
|
22
|
-
execute: Annotated[
|
|
23
|
-
bool,
|
|
24
|
-
typer.Option(
|
|
25
|
-
help="Run the command if suggested. Dangerous!",
|
|
26
|
-
),
|
|
27
|
-
] = False,
|
|
28
|
-
):
|
|
29
|
-
"""Suggest a command for a Linux task described in QUERY.
|
|
30
|
-
|
|
31
|
-
Example: ape "Create a symbolic link named 'win' pointing to /mnt/c/Users/jdoe"
|
|
32
|
-
|
|
33
|
-
Output : ln -s /mnt/c/Users/jdoe win
|
|
34
|
-
"""
|
|
35
|
-
|
|
36
|
-
console = rich.console.Console()
|
|
37
|
-
|
|
38
|
-
try:
|
|
39
|
-
with console.status("[bold][blue]Processing ...", spinner="monkey"):
|
|
40
|
-
answer = llm.find_answer(query, model)
|
|
41
|
-
except llm.EmptyQueryError:
|
|
42
|
-
typer.echo("Query cannot be empty.", err=True)
|
|
43
|
-
raise typer.Exit(1)
|
|
44
|
-
except llm.OpenAIAPIStatusError as e:
|
|
45
|
-
typer.echo(
|
|
46
|
-
f"OpenAI {e.status_code} error: {e.message}",
|
|
47
|
-
err=True,
|
|
48
|
-
)
|
|
49
|
-
raise typer.Exit(1)
|
|
50
|
-
except openai.OpenAIError as e:
|
|
51
|
-
typer.echo(f"Generic OpenAI error: {e}", err=True)
|
|
52
|
-
raise typer.Exit(1)
|
|
53
|
-
|
|
54
|
-
if llm.no_answer(answer):
|
|
55
|
-
typer.echo(answer, err=True)
|
|
56
|
-
raise typer.Exit(1)
|
|
57
|
-
|
|
58
|
-
typer.echo(answer)
|
|
59
|
-
|
|
60
|
-
if execute:
|
|
61
|
-
subprocess.check_call(answer, shell=True)
|
ape_linux-0.1.1/src/ape/llm.py
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import openai
|
|
2
|
-
|
|
3
|
-
SYSTEM_PROMPT = """\
|
|
4
|
-
You are a Linux command assistant. You will be asked a question about how to perform a task in Linux or Unix-like operating systems. You should only include in your answer the command or commands to perform the task. If you do not know how to perform the task, output "Please rephrase.".
|
|
5
|
-
|
|
6
|
-
Here are a few examples.
|
|
7
|
-
|
|
8
|
-
Question: List all the files and directories in projects in my home directory
|
|
9
|
-
Answer: ls ~/projects
|
|
10
|
-
|
|
11
|
-
Question: What is my username?
|
|
12
|
-
Answer: whoami
|
|
13
|
-
|
|
14
|
-
Question: Find all files with the extension .txt under the current working directory
|
|
15
|
-
Answer: find . -name "*.txt"
|
|
16
|
-
|
|
17
|
-
Question: What is the captial of France?
|
|
18
|
-
Answer: Please rephrase.
|
|
19
|
-
|
|
20
|
-
Question: Tell me a story
|
|
21
|
-
Answer: Please rephrase.""" # noqa: E501
|
|
22
|
-
|
|
23
|
-
USER_PROMPT_TEMPLATE = """\
|
|
24
|
-
Question: {query}
|
|
25
|
-
Answer:"""
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
class EmptyQueryError(ValueError):
|
|
29
|
-
pass
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
class OpenAIAPIStatusError(Exception):
|
|
33
|
-
def __init__(self, message: str, status_code: int) -> None:
|
|
34
|
-
self.message = message
|
|
35
|
-
self.status_code = status_code
|
|
36
|
-
super().__init__(self.message, self.status_code)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def make_user_prompt(query: str) -> str:
|
|
40
|
-
query = query.strip()
|
|
41
|
-
if query == "":
|
|
42
|
-
raise EmptyQueryError()
|
|
43
|
-
return USER_PROMPT_TEMPLATE.format(query=query)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def call_llm(user_prompt: str, model: str, system_prompt: str) -> str | None:
|
|
47
|
-
client = openai.OpenAI()
|
|
48
|
-
|
|
49
|
-
try:
|
|
50
|
-
response = client.chat.completions.create(
|
|
51
|
-
model=model,
|
|
52
|
-
messages=[
|
|
53
|
-
{"role": "system", "content": system_prompt},
|
|
54
|
-
{"role": "user", "content": user_prompt},
|
|
55
|
-
],
|
|
56
|
-
)
|
|
57
|
-
except openai.APIStatusError as e:
|
|
58
|
-
raise OpenAIAPIStatusError(e.response.json()["error"]["message"], e.status_code)
|
|
59
|
-
|
|
60
|
-
answer = response.choices[0].message.content
|
|
61
|
-
return answer
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def find_answer(query: str, model: str) -> str:
|
|
65
|
-
user_prompt = make_user_prompt(query)
|
|
66
|
-
|
|
67
|
-
answer = call_llm(user_prompt, model, SYSTEM_PROMPT)
|
|
68
|
-
|
|
69
|
-
if answer is None:
|
|
70
|
-
return "Please rephrase."
|
|
71
|
-
else:
|
|
72
|
-
return answer # this can also be "Please rephrase."
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def no_answer(answer: str) -> bool:
|
|
76
|
-
return answer.strip().lower().rstrip(".") == "please rephrase"
|
|
File without changes
|