tass 0.1.12__tar.gz → 0.1.13__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.
- {tass-0.1.12 → tass-0.1.13}/PKG-INFO +9 -1
- {tass-0.1.12 → tass-0.1.13}/README.md +8 -0
- {tass-0.1.12 → tass-0.1.13}/pyproject.toml +32 -2
- {tass-0.1.12 → tass-0.1.13}/src/app.py +19 -6
- tass-0.1.13/src/cli.py +27 -0
- tass-0.1.13/src/constants.py +73 -0
- {tass-0.1.12 → tass-0.1.13}/src/llm_client.py +4 -6
- {tass-0.1.12 → tass-0.1.13}/src/tools/edit_file.py +11 -9
- {tass-0.1.12 → tass-0.1.13}/src/tools/execute.py +4 -10
- {tass-0.1.12 → tass-0.1.13}/src/tools/read_file.py +2 -3
- {tass-0.1.12 → tass-0.1.13}/tests/test_fuzzy_match.py +20 -3
- {tass-0.1.12 → tass-0.1.13}/tests/test_utils.py +1 -1
- {tass-0.1.12 → tass-0.1.13}/uv.lock +1 -1
- tass-0.1.12/src/cli.py +0 -6
- tass-0.1.12/src/constants.py +0 -17
- {tass-0.1.12 → tass-0.1.13}/.gitignore +0 -0
- {tass-0.1.12 → tass-0.1.13}/.python-version +0 -0
- {tass-0.1.12 → tass-0.1.13}/LICENSE +0 -0
- {tass-0.1.12 → tass-0.1.13}/assets/tass.gif +0 -0
- {tass-0.1.12 → tass-0.1.13}/src/__init__.py +0 -0
- {tass-0.1.12 → tass-0.1.13}/src/tools/__init__.py +0 -0
- {tass-0.1.12 → tass-0.1.13}/src/utils.py +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tass
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.13
|
|
4
4
|
Summary: A terminal assistant that allows you to ask an LLM to run commands.
|
|
5
5
|
Project-URL: Homepage, https://github.com/cetincan0/tass
|
|
6
6
|
Author: Can Cetin
|
|
@@ -44,6 +44,12 @@ You can run it with
|
|
|
44
44
|
tass
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
+
or if you only want to ask/request a single thing
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
tass "convert video.mp4 to audio.mp3"
|
|
51
|
+
```
|
|
52
|
+
|
|
47
53
|
tass has only been tested with llama.cpp with LLMs such as gpt-oss-120b and MiniMax M2.1, but any LLM with tool calling capabilities should work.
|
|
48
54
|
|
|
49
55
|
By default, tass will try connecting to http://localhost:8080. To use another host, set the `TASS_HOST` environment variable. If your server requires an API key, you can set the `TASS_API_KEY` environment variable. At the moment there's no support for connecting tass to a non-local API, nor are there plans for it. I plan on keeping tass completely local. There's no telemetry, no logs, just a simple REPL loop.
|
|
@@ -52,6 +58,8 @@ Once it's running, you can ask questions or give commands like "Create an empty
|
|
|
52
58
|
|
|
53
59
|
You can enter multiline input by ending lines with a backslash (\\). The continuation prompt will keep appearing until you enter a line without a trailing backslash.
|
|
54
60
|
|
|
61
|
+
You can use the --yolo flag to turn off user confirmations for executing commands and editing files, but I would only recommend using this if you're benchmarking tass with an LLM and highly recommend not using it outside of testing/benchmarking scenarios.
|
|
62
|
+
|
|
55
63
|
## Upgrade
|
|
56
64
|
|
|
57
65
|
### Using uv
|
|
@@ -30,6 +30,12 @@ You can run it with
|
|
|
30
30
|
tass
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
+
or if you only want to ask/request a single thing
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
tass "convert video.mp4 to audio.mp3"
|
|
37
|
+
```
|
|
38
|
+
|
|
33
39
|
tass has only been tested with llama.cpp with LLMs such as gpt-oss-120b and MiniMax M2.1, but any LLM with tool calling capabilities should work.
|
|
34
40
|
|
|
35
41
|
By default, tass will try connecting to http://localhost:8080. To use another host, set the `TASS_HOST` environment variable. If your server requires an API key, you can set the `TASS_API_KEY` environment variable. At the moment there's no support for connecting tass to a non-local API, nor are there plans for it. I plan on keeping tass completely local. There's no telemetry, no logs, just a simple REPL loop.
|
|
@@ -38,6 +44,8 @@ Once it's running, you can ask questions or give commands like "Create an empty
|
|
|
38
44
|
|
|
39
45
|
You can enter multiline input by ending lines with a backslash (\\). The continuation prompt will keep appearing until you enter a line without a trailing backslash.
|
|
40
46
|
|
|
47
|
+
You can use the --yolo flag to turn off user confirmations for executing commands and editing files, but I would only recommend using this if you're benchmarking tass with an LLM and highly recommend not using it outside of testing/benchmarking scenarios.
|
|
48
|
+
|
|
41
49
|
## Upgrade
|
|
42
50
|
|
|
43
51
|
### Using uv
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "tass"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.13"
|
|
4
4
|
description = "A terminal assistant that allows you to ask an LLM to run commands."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -35,8 +35,38 @@ dev = [
|
|
|
35
35
|
"pytest>=9.0.2",
|
|
36
36
|
]
|
|
37
37
|
|
|
38
|
+
[tool.ruff]
|
|
39
|
+
target-version = "py310"
|
|
40
|
+
line-length = 120
|
|
41
|
+
|
|
42
|
+
[tool.ruff.lint]
|
|
43
|
+
select = [
|
|
44
|
+
"A",
|
|
45
|
+
"B",
|
|
46
|
+
"C4",
|
|
47
|
+
"E",
|
|
48
|
+
"F",
|
|
49
|
+
"I",
|
|
50
|
+
"ISC",
|
|
51
|
+
"SIM",
|
|
52
|
+
"RUF",
|
|
53
|
+
"UP",
|
|
54
|
+
"W",
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
ignore = [
|
|
58
|
+
"E501",
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
[tool.ruff.lint.isort]
|
|
62
|
+
known-first-party = ["tass"]
|
|
63
|
+
|
|
64
|
+
[tool.ruff.format]
|
|
65
|
+
quote-style = "double"
|
|
66
|
+
indent-style = "space"
|
|
67
|
+
|
|
38
68
|
[tool.pyright]
|
|
39
69
|
pythonVersion = "3.10"
|
|
40
|
-
include = ["src/**/*.py"]
|
|
70
|
+
include = ["src/**/*.py", "tests/**/*.py"]
|
|
41
71
|
venvPath = "."
|
|
42
72
|
venv = ".venv"
|
|
@@ -28,7 +28,8 @@ from src.utils import (
|
|
|
28
28
|
|
|
29
29
|
class TassApp:
|
|
30
30
|
|
|
31
|
-
def __init__(self):
|
|
31
|
+
def __init__(self, yolo_mode: bool = False):
|
|
32
|
+
self.yolo_mode = yolo_mode
|
|
32
33
|
self.messages: list[dict] = [{"role": "system", "content": SYSTEM_PROMPT}]
|
|
33
34
|
self.llm_client = LLMClient()
|
|
34
35
|
self.key_bindings = create_key_bindings()
|
|
@@ -80,7 +81,7 @@ class TassApp:
|
|
|
80
81
|
|
|
81
82
|
console.print("\n - Summarizing conversation...")
|
|
82
83
|
response = self.llm_client.get_chat_completions(
|
|
83
|
-
messages=self.messages
|
|
84
|
+
messages=[*self.messages, {"role": "user", "content": prompt}],
|
|
84
85
|
tools=[
|
|
85
86
|
EDIT_FILE_TOOL,
|
|
86
87
|
EXECUTE_TOOL,
|
|
@@ -154,7 +155,7 @@ class TassApp:
|
|
|
154
155
|
live.update(generate_layout())
|
|
155
156
|
|
|
156
157
|
delta = chunk["choices"][0]["delta"]
|
|
157
|
-
if not any(
|
|
158
|
+
if not any(delta.get(key) for key in ["content", "reasoning_content", "tool_calls"]):
|
|
158
159
|
continue
|
|
159
160
|
|
|
160
161
|
if delta.get("reasoning_content"):
|
|
@@ -208,6 +209,7 @@ class TassApp:
|
|
|
208
209
|
for tool_call in tool_calls_map.values():
|
|
209
210
|
tool = self.TOOLS_MAP[tool_call["function"]["name"]]
|
|
210
211
|
tool_args = json.loads(tool_call["function"]["arguments"])
|
|
212
|
+
tool_args["yolo_mode"] = self.yolo_mode
|
|
211
213
|
result = tool(**tool_args)
|
|
212
214
|
self.messages.append(
|
|
213
215
|
{
|
|
@@ -223,7 +225,19 @@ class TassApp:
|
|
|
223
225
|
console.print(f" [red]Tool call failed: {str(e).strip()}[/red]")
|
|
224
226
|
return self.call_llm()
|
|
225
227
|
|
|
226
|
-
def run(self):
|
|
228
|
+
def run(self, initial_input: str | None = None):
|
|
229
|
+
if initial_input:
|
|
230
|
+
self.messages.append({"role": "user", "content": initial_input})
|
|
231
|
+
while True:
|
|
232
|
+
try:
|
|
233
|
+
finished = self.call_llm()
|
|
234
|
+
except Exception as e:
|
|
235
|
+
console.print(f"Failed to call LLM: {e}")
|
|
236
|
+
break
|
|
237
|
+
|
|
238
|
+
if finished:
|
|
239
|
+
return
|
|
240
|
+
|
|
227
241
|
try:
|
|
228
242
|
self.check_llm_host()
|
|
229
243
|
except KeyboardInterrupt:
|
|
@@ -258,12 +272,11 @@ class TassApp:
|
|
|
258
272
|
break
|
|
259
273
|
|
|
260
274
|
self.messages.append({"role": "user", "content": user_input})
|
|
261
|
-
|
|
262
275
|
while True:
|
|
263
276
|
try:
|
|
264
277
|
finished = self.call_llm()
|
|
265
278
|
except Exception as e:
|
|
266
|
-
console.print(f"Failed to call LLM: {
|
|
279
|
+
console.print(f"Failed to call LLM: {e}")
|
|
267
280
|
break
|
|
268
281
|
|
|
269
282
|
if finished:
|
tass-0.1.13/src/cli.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
|
|
3
|
+
from src.app import TassApp
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def main():
|
|
7
|
+
parser = argparse.ArgumentParser(
|
|
8
|
+
description="Terminal Assistant - Ask an LLM to run commands"
|
|
9
|
+
)
|
|
10
|
+
parser.add_argument(
|
|
11
|
+
"--yolo",
|
|
12
|
+
action="store_true",
|
|
13
|
+
help="YOLO mode: execute all commands and edit files without asking for confirmation",
|
|
14
|
+
)
|
|
15
|
+
parser.add_argument(
|
|
16
|
+
"prompt",
|
|
17
|
+
nargs="?",
|
|
18
|
+
help="Prompt to run (enclose in quotes; runs in single-shot mode and exits)",
|
|
19
|
+
)
|
|
20
|
+
args = parser.parse_args()
|
|
21
|
+
|
|
22
|
+
app = TassApp(yolo_mode=args.yolo)
|
|
23
|
+
|
|
24
|
+
if args.prompt:
|
|
25
|
+
app.run(initial_input=args.prompt)
|
|
26
|
+
else:
|
|
27
|
+
app.run()
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import platform
|
|
3
|
+
import subprocess
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
|
|
8
|
+
console = Console()
|
|
9
|
+
CWD_PATH = Path.cwd().resolve()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_shell_info() -> str:
|
|
13
|
+
shell_path = os.environ.get('SHELL', '')
|
|
14
|
+
shell_name = shell_path.split('/')[-1] if shell_path else 'unknown'
|
|
15
|
+
return shell_name
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_git_info() -> str:
|
|
19
|
+
try:
|
|
20
|
+
git_info = []
|
|
21
|
+
|
|
22
|
+
subprocess.run(['git', 'rev-parse', '--git-dir'], capture_output=True, check=True, cwd=CWD_PATH)
|
|
23
|
+
result = subprocess.run(['git', 'branch', '--show-current'], capture_output=True, text=True, cwd=CWD_PATH)
|
|
24
|
+
branch = result.stdout.strip()
|
|
25
|
+
if branch:
|
|
26
|
+
git_info.append(f"Branch: {branch}")
|
|
27
|
+
|
|
28
|
+
result = subprocess.run(['git', 'status', '--porcelain'], capture_output=True, text=True, cwd=CWD_PATH)
|
|
29
|
+
has_changes = bool(result.stdout.strip())
|
|
30
|
+
git_info.append(f"Uncommitted changes: {'Yes' if has_changes else 'No'}")
|
|
31
|
+
|
|
32
|
+
return '\n'.join(git_info)
|
|
33
|
+
except Exception:
|
|
34
|
+
return "Not a git repository"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_directory_listing() -> str:
|
|
38
|
+
try:
|
|
39
|
+
entries = []
|
|
40
|
+
iterdir = CWD_PATH.iterdir()
|
|
41
|
+
for count, entry in enumerate(sorted(iterdir)):
|
|
42
|
+
if count >= 100:
|
|
43
|
+
entries.append(f"... and {len(list(iterdir)) - 100} more items")
|
|
44
|
+
break
|
|
45
|
+
if entry.is_dir():
|
|
46
|
+
entries.append(f"{entry.name}/")
|
|
47
|
+
else:
|
|
48
|
+
entries.append(entry.name)
|
|
49
|
+
return '\n'.join(entries) if entries else "Empty directory"
|
|
50
|
+
except PermissionError:
|
|
51
|
+
return "Permission denied"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
SYSTEM_PROMPT = f"""You are tass, or Terminal Assistant, a helpful AI that executes shell commands based on natural-language requests.
|
|
55
|
+
|
|
56
|
+
If the user's request involves making changes to the filesystem such as creating or deleting files or directories, you MUST first check whether the file or directory exists before proceeding.
|
|
57
|
+
|
|
58
|
+
If a user asks for an answer or explanation to something instead of requesting to run a command, answer briefly and concisely. Do not supply extra information, suggestions, tips, or anything of the sort.
|
|
59
|
+
|
|
60
|
+
This app has a feature where the user can refer to files or directories by typing @ which will open an file autocomplete dropdown. When this feature is used, the @ will remain in the filename. When working with said file, ignore the preceding @.
|
|
61
|
+
|
|
62
|
+
Current working directory: {CWD_PATH}
|
|
63
|
+
|
|
64
|
+
# Directory Context
|
|
65
|
+
OS: {platform.system()}
|
|
66
|
+
Shell: {get_shell_info()}
|
|
67
|
+
|
|
68
|
+
# Git Info
|
|
69
|
+
{get_git_info()}
|
|
70
|
+
|
|
71
|
+
# Directory Listing
|
|
72
|
+
{get_directory_listing()}
|
|
73
|
+
"""
|
|
@@ -14,7 +14,6 @@ class LLMClient:
|
|
|
14
14
|
self,
|
|
15
15
|
method: Literal["get", "post"],
|
|
16
16
|
url: str,
|
|
17
|
-
*args,
|
|
18
17
|
**kwargs,
|
|
19
18
|
):
|
|
20
19
|
return requests.request(
|
|
@@ -23,15 +22,14 @@ class LLMClient:
|
|
|
23
22
|
headers={
|
|
24
23
|
"Authorization": f"Bearer {self.api_key}",
|
|
25
24
|
},
|
|
26
|
-
*args,
|
|
27
25
|
**kwargs,
|
|
28
26
|
)
|
|
29
27
|
|
|
30
|
-
def get(self, url: str,
|
|
31
|
-
return self.request("get", url,
|
|
28
|
+
def get(self, url: str, **kwargs):
|
|
29
|
+
return self.request("get", url, **kwargs)
|
|
32
30
|
|
|
33
|
-
def post(self, url: str,
|
|
34
|
-
return self.request("post", url,
|
|
31
|
+
def post(self, url: str, **kwargs):
|
|
32
|
+
return self.request("post", url, **kwargs)
|
|
35
33
|
|
|
36
34
|
def get_models(self):
|
|
37
35
|
return self.get("/v1/models", timeout=2)
|
|
@@ -6,7 +6,6 @@ from rich.markdown import Markdown
|
|
|
6
6
|
|
|
7
7
|
from src.constants import console
|
|
8
8
|
|
|
9
|
-
|
|
10
9
|
EDIT_FILE_TOOL = {
|
|
11
10
|
"type": "function",
|
|
12
11
|
"function": {
|
|
@@ -80,7 +79,7 @@ def fuzzy_match(edit_find: str, lines: list[str]) -> tuple[int, int] | None:
|
|
|
80
79
|
if best_ratio == 1.0:
|
|
81
80
|
return best_start + 1, best_start + best_window_len
|
|
82
81
|
|
|
83
|
-
if best_ratio >= 0.
|
|
82
|
+
if best_ratio >= 0.9:
|
|
84
83
|
return best_start + 1, best_start + best_window_len
|
|
85
84
|
|
|
86
85
|
return None
|
|
@@ -100,7 +99,9 @@ def convert_edit_to_line_edit(edit: dict, original_content: str) -> LineEdit:
|
|
|
100
99
|
|
|
101
100
|
# Then try matching without spacing
|
|
102
101
|
for i in range(len(lines)):
|
|
103
|
-
|
|
102
|
+
stripped_lines = [line.strip() for line in lines[i : i + len(edit_lines)]]
|
|
103
|
+
stripped_edit_lines = [line.strip() for line in edit_lines]
|
|
104
|
+
if stripped_lines == stripped_edit_lines:
|
|
104
105
|
return LineEdit(i + 1, i + len(edit_lines), edit["replace"], False)
|
|
105
106
|
|
|
106
107
|
# Finally try sequence matching while ignoring whitespace
|
|
@@ -111,7 +112,7 @@ def convert_edit_to_line_edit(edit: dict, original_content: str) -> LineEdit:
|
|
|
111
112
|
raise Exception("Edit not found in file")
|
|
112
113
|
|
|
113
114
|
|
|
114
|
-
def edit_file(path: str, edits: list[dict]) -> str:
|
|
115
|
+
def edit_file(path: str, edits: list[dict], yolo_mode: bool = False) -> str:
|
|
115
116
|
def find_line_edit(n: int) -> LineEdit | None:
|
|
116
117
|
for line_edit in line_edits:
|
|
117
118
|
if line_edit.line_start <= n <= line_edit.line_end:
|
|
@@ -121,7 +122,7 @@ def edit_file(path: str, edits: list[dict]) -> str:
|
|
|
121
122
|
|
|
122
123
|
file_exists = Path(path).exists()
|
|
123
124
|
if file_exists:
|
|
124
|
-
with open(path
|
|
125
|
+
with open(path) as f:
|
|
125
126
|
original_content = f.read()
|
|
126
127
|
else:
|
|
127
128
|
original_content = ""
|
|
@@ -160,11 +161,12 @@ def edit_file(path: str, edits: list[dict]) -> str:
|
|
|
160
161
|
|
|
161
162
|
console.print()
|
|
162
163
|
console.print(Markdown(f"```diff\n{unified_diff_md}\n```"))
|
|
163
|
-
answer = console.input("\n[bold]Run?[/] ([bold]Y[/]/n): ").strip().lower()
|
|
164
|
-
if answer not in ("yes", "y", ""):
|
|
165
|
-
reason = console.input("Why not? (optional, press Enter to skip): ").strip()
|
|
166
|
-
return f"User declined: {reason or 'no reason'}"
|
|
167
164
|
|
|
165
|
+
if not yolo_mode:
|
|
166
|
+
answer = console.input("\n[bold]Run?[/] ([bold]Y[/]/n): ").strip().lower()
|
|
167
|
+
if answer not in ("yes", "y", ""):
|
|
168
|
+
reason = console.input("Why not? (optional, press Enter to skip): ").strip()
|
|
169
|
+
return f"User declined: {reason or 'no reason'}"
|
|
168
170
|
console.print(" └ Running...")
|
|
169
171
|
try:
|
|
170
172
|
with open(path, "w") as f:
|
|
@@ -64,16 +64,12 @@ def is_read_only_command(command: str) -> bool:
|
|
|
64
64
|
command = command.replace(";", "|")
|
|
65
65
|
|
|
66
66
|
pipes = command.split("|")
|
|
67
|
-
for pipe in pipes
|
|
68
|
-
if pipe.strip().split()[0] not in READ_ONLY_COMMANDS:
|
|
69
|
-
return False
|
|
67
|
+
return all(pipe.strip().split()[0] in READ_ONLY_COMMANDS for pipe in pipes)
|
|
70
68
|
|
|
71
|
-
return True
|
|
72
69
|
|
|
73
|
-
|
|
74
|
-
def execute(command: str, explanation: str) -> str:
|
|
70
|
+
def execute(command: str, explanation: str, yolo_mode: bool = False) -> str:
|
|
75
71
|
command = command.strip()
|
|
76
|
-
requires_confirmation = not is_read_only_command(command)
|
|
72
|
+
requires_confirmation = not yolo_mode and not is_read_only_command(command)
|
|
77
73
|
if requires_confirmation:
|
|
78
74
|
console.print()
|
|
79
75
|
console.print(Markdown(f"```shell\n{command}\n```"))
|
|
@@ -83,11 +79,9 @@ def execute(command: str, explanation: str) -> str:
|
|
|
83
79
|
if answer not in ("yes", "y", ""):
|
|
84
80
|
reason = console.input("Why not? (optional, press Enter to skip): ").strip()
|
|
85
81
|
return f"User declined: {reason or 'no reason'}"
|
|
86
|
-
|
|
87
|
-
if requires_confirmation:
|
|
88
82
|
console.print(" └ Running...")
|
|
89
83
|
else:
|
|
90
|
-
console.print(f" └ Running [bold]{command}[/] (Explanation: {explanation})
|
|
84
|
+
console.print(f" └ Running [bold]{command}[/] (Explanation: {explanation})")
|
|
91
85
|
|
|
92
86
|
try:
|
|
93
87
|
result = subprocess.run(
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from src.constants import console
|
|
2
2
|
|
|
3
|
-
|
|
4
3
|
READ_FILE_TOOL = {
|
|
5
4
|
"type": "function",
|
|
6
5
|
"function": {
|
|
@@ -31,7 +30,7 @@ READ_FILE_TOOL = {
|
|
|
31
30
|
}
|
|
32
31
|
|
|
33
32
|
|
|
34
|
-
def read_file(path: str, start: int = 1, num_lines: int = 1000) -> str:
|
|
33
|
+
def read_file(path: str, start: int = 1, num_lines: int = 1000, yolo_mode: bool = False) -> str:
|
|
35
34
|
if start == 1 and num_lines == 1000:
|
|
36
35
|
console.print(f" └ Reading file [bold]{path}[/]...")
|
|
37
36
|
else:
|
|
@@ -59,4 +58,4 @@ def read_file(path: str, start: int = 1, num_lines: int = 1000) -> str:
|
|
|
59
58
|
except Exception as e:
|
|
60
59
|
console.print(" [red]read_file failed[/red]")
|
|
61
60
|
console.print(f" [red]{str(e).strip()}[/red]")
|
|
62
|
-
return f"read_file failed: {
|
|
61
|
+
return f"read_file failed: {e}"
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from src.tools.edit_file import fuzzy_match
|
|
2
2
|
|
|
3
|
-
|
|
4
3
|
file_contents = """def fib(n):
|
|
5
4
|
'''Return the nth Fibonacci number (iterative).'''
|
|
6
5
|
if n <= 0:
|
|
@@ -22,7 +21,25 @@ if __name__ == "__main__":
|
|
|
22
21
|
def test_fuzzy():
|
|
23
22
|
edit_find = " return b\n\n\n\nif __name__ == \"__main__\":"
|
|
24
23
|
lines = file_contents.split("\n")
|
|
25
|
-
|
|
24
|
+
assert fuzzy_match(edit_find, lines) == (10, 13)
|
|
25
|
+
|
|
26
|
+
edit_find = """def fib(n):
|
|
27
|
+
|
|
28
|
+
'''Return the nth Fibonacci number iterative.'''
|
|
29
|
+
|
|
30
|
+
if n<= 0:
|
|
31
|
+
|
|
32
|
+
return 0
|
|
33
|
+
|
|
34
|
+
if n ==1:
|
|
35
|
+
|
|
36
|
+
return 1
|
|
37
|
+
|
|
38
|
+
a, b = 0, 1
|
|
39
|
+
|
|
40
|
+
for _ in range(2, n + 1):
|
|
41
|
+
|
|
42
|
+
a, b = b, a + b
|
|
26
43
|
|
|
27
|
-
|
|
44
|
+
return b"""
|
|
28
45
|
assert fuzzy_match(edit_find, lines) == (1, 10)
|
tass-0.1.12/src/cli.py
DELETED
tass-0.1.12/src/constants.py
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
|
|
3
|
-
from rich.console import Console
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
console = Console()
|
|
7
|
-
CWD_PATH = Path.cwd().resolve()
|
|
8
|
-
|
|
9
|
-
SYSTEM_PROMPT = f"""You are tass, or Terminal Assistant, a helpful AI that executes shell commands based on natural-language requests.
|
|
10
|
-
|
|
11
|
-
If the user's request involves making changes to the filesystem such as creating or deleting files or directories, you MUST first check whether the file or directory exists before proceeding.
|
|
12
|
-
|
|
13
|
-
If a user asks for an answer or explanation to something instead of requesting to run a command, answer briefly and concisely. Do not supply extra information, suggestions, tips, or anything of the sort.
|
|
14
|
-
|
|
15
|
-
This app has a feature where the user can refer to files or directories by typing @ which will open an file autocomplete dropdown. When this feature is used, the @ will remain in the filename. When working with said file, ignore the preceding @.
|
|
16
|
-
|
|
17
|
-
Current working directory: {CWD_PATH}"""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|