ape-linux 0.1.2__tar.gz → 0.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ape-linux
3
- Version: 0.1.2
3
+ Version: 0.2.1
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 OPENAI_API_KEY=key
52
+ export APE_OPENAI_API_KEY=key
53
53
  ```
54
54
 
55
55
  To run:
@@ -88,7 +88,7 @@ you should get:
88
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 OPENAI_API_KEY=key
26
+ export APE_OPENAI_API_KEY=key
27
27
  ```
28
28
 
29
29
  To run:
@@ -62,7 +62,7 @@ you should get:
62
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.1/ape.py ADDED
@@ -0,0 +1,106 @@
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(
15
+ api_key: str, model: str, system_prompt: str, user_prompt: str
16
+ ) -> str | None:
17
+ return (
18
+ openai.OpenAI(api_key=api_key)
19
+ .chat.completions.create(
20
+ model=model,
21
+ messages=[
22
+ {"role": "system", "content": system_prompt},
23
+ {"role": "user", "content": user_prompt},
24
+ ],
25
+ )
26
+ .choices[0]
27
+ .message.content
28
+ )
29
+
30
+
31
+ @app.command()
32
+ def main(
33
+ query: Annotated[
34
+ str, typer.Argument(help="Query describing a Linux task.", show_default=False)
35
+ ],
36
+ model: Annotated[
37
+ str,
38
+ typer.Option(
39
+ "--model",
40
+ "-m",
41
+ help="OpenAI model. See https://platform.openai.com/docs/models.",
42
+ ),
43
+ ] = "gpt-4o",
44
+ execute: Annotated[
45
+ bool,
46
+ typer.Option(
47
+ "--execute",
48
+ "-e",
49
+ help="Run the command if suggested. Dangerous!",
50
+ ),
51
+ ] = False,
52
+ ):
53
+ """Suggest a command for a Linux task described in QUERY.
54
+
55
+ Example: ape "Create a symbolic link named 'win' pointing to /mnt/c/Users/jdoe"
56
+
57
+ Output : ln -s /mnt/c/Users/jdoe win
58
+ """
59
+
60
+ console = rich.console.Console()
61
+
62
+ system_prompt = """\
63
+ 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."".
64
+
65
+ Here are a few examples.
66
+
67
+ Question: List all the files and directories in projects in my home directory
68
+ Answer: ls ~/projects
69
+
70
+ Question: What is my username?
71
+ Answer: whoami
72
+
73
+ Question: Find all files with the extension .txt under the current working directory
74
+ Answer: find . -name "*.txt"
75
+
76
+ Question: What is the captial of France?
77
+ Answer: echo "Please try again."
78
+
79
+ Question: Tell me a story
80
+ Answer: echo "Please try again.\"""" # noqa: E501
81
+
82
+ user_prompt = f"""\
83
+ Question: {query.strip()}
84
+ Answer:"""
85
+
86
+ try:
87
+ api_key = os.environ["APE_OPENAI_API_KEY"]
88
+ except KeyError:
89
+ typer.echo("Set the environment variable APE_OPENAI_API_KEY.", err=True)
90
+ raise typer.Exit(1)
91
+
92
+ try:
93
+ with console.status("[bold][blue]Processing ...", spinner="monkey"):
94
+ answer = call_llm(api_key, model, system_prompt, user_prompt)
95
+ if answer is None:
96
+ answer = 'echo "Please try again."'
97
+ typer.echo(answer)
98
+ if execute:
99
+ subprocess.check_call(answer, shell=True)
100
+ except openai.APIStatusError as error:
101
+ typer.echo(
102
+ f"OpenAI {error.status_code} error: "
103
+ f"{error.response.json()['error']['message']}",
104
+ err=True,
105
+ )
106
+ raise typer.Exit(1)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "ape-linux"
3
- version = "0.1.2"
3
+ version = "0.2.1"
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", from = "src" },
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.cli:app"
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."""
@@ -1,57 +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
- typer.echo(answer)
55
-
56
- if execute:
57
- subprocess.check_call(answer, shell=True)
@@ -1,72 +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 "echo "Please try again."".
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: echo "Please try again."
19
-
20
- Question: Tell me a story
21
- Answer: echo "Please try again.\"""" # 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 'echo "Please try again."'
71
- else:
72
- return answer
File without changes