tass 0.1.4__py3-none-any.whl → 0.1.6__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.
- src/app.py +65 -19
- src/constants.py +24 -10
- {tass-0.1.4.dist-info → tass-0.1.6.dist-info}/METADATA +2 -2
- tass-0.1.6.dist-info/RECORD +10 -0
- tass-0.1.4.dist-info/RECORD +0 -10
- {tass-0.1.4.dist-info → tass-0.1.6.dist-info}/WHEEL +0 -0
- {tass-0.1.4.dist-info → tass-0.1.6.dist-info}/entry_points.txt +0 -0
- {tass-0.1.4.dist-info → tass-0.1.6.dist-info}/licenses/LICENSE +0 -0
src/app.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import os
|
|
3
3
|
import subprocess
|
|
4
|
+
from pathlib import Path
|
|
4
5
|
|
|
5
6
|
import requests
|
|
6
7
|
from rich.console import Console
|
|
@@ -56,7 +57,7 @@ class TassApp:
|
|
|
56
57
|
console.print(f"[red]Unable to verify new host {self.host}. Continuing with it anyway.[/red]")
|
|
57
58
|
|
|
58
59
|
def summarize(self):
|
|
59
|
-
max_messages =
|
|
60
|
+
max_messages = 20
|
|
60
61
|
if len(self.messages) <= max_messages:
|
|
61
62
|
return
|
|
62
63
|
|
|
@@ -124,16 +125,30 @@ class TassApp:
|
|
|
124
125
|
return self.call_llm()
|
|
125
126
|
|
|
126
127
|
def read_file(self, path: str, start: int = 1) -> str:
|
|
127
|
-
|
|
128
|
+
if start == 1:
|
|
129
|
+
console.print(f" └ Reading file [bold]{path}[/]...")
|
|
130
|
+
else:
|
|
131
|
+
console.print(f" └ Reading file [bold]{path}[/] (from line {start})...")
|
|
128
132
|
|
|
129
133
|
try:
|
|
130
|
-
|
|
131
|
-
|
|
134
|
+
result = subprocess.run(
|
|
135
|
+
f"cat -n {path}",
|
|
136
|
+
shell=True,
|
|
137
|
+
capture_output=True,
|
|
138
|
+
text=True,
|
|
139
|
+
)
|
|
132
140
|
except Exception as e:
|
|
133
141
|
console.print(" [red]read_file failed[/red]")
|
|
134
142
|
console.print(f" [red]{str(e)}[/red]")
|
|
135
143
|
return f"read_file failed: {str(e)}"
|
|
136
144
|
|
|
145
|
+
out = result.stdout
|
|
146
|
+
err = result.stderr
|
|
147
|
+
if result.returncode != 0:
|
|
148
|
+
console.print(" [red]read_file failed[/red]")
|
|
149
|
+
console.print(f" [red]{err}[/red]")
|
|
150
|
+
return f"read_file failed: {err}"
|
|
151
|
+
|
|
137
152
|
lines = []
|
|
138
153
|
line_num = 1
|
|
139
154
|
for line in out.split("\n"):
|
|
@@ -151,11 +166,52 @@ class TassApp:
|
|
|
151
166
|
console.print(" [green]Command succeeded[/green]")
|
|
152
167
|
return "".join(lines)
|
|
153
168
|
|
|
154
|
-
def edit_file(self, path: str,
|
|
155
|
-
|
|
156
|
-
|
|
169
|
+
def edit_file(self, path: str, edits: list[dict]) -> str:
|
|
170
|
+
for edit in edits:
|
|
171
|
+
edit["applied"] = False
|
|
172
|
+
|
|
173
|
+
def find_edit(n: int) -> dict | None:
|
|
174
|
+
for edit in edits:
|
|
175
|
+
if edit["line_start"] <= n <= edit["line_end"]:
|
|
176
|
+
return edit
|
|
177
|
+
|
|
178
|
+
return None
|
|
179
|
+
|
|
180
|
+
file_exists = Path(path).exists()
|
|
181
|
+
if file_exists:
|
|
182
|
+
with open(path, "r") as f:
|
|
183
|
+
original_content = f.read()
|
|
184
|
+
else:
|
|
185
|
+
original_content = ""
|
|
186
|
+
|
|
187
|
+
final_lines = []
|
|
188
|
+
original_lines = original_content.split("\n")
|
|
189
|
+
diff_text = f"{'Editing' if file_exists else 'Creating'} {path}"
|
|
190
|
+
for i, line in enumerate(original_lines):
|
|
191
|
+
line_num = i + 1
|
|
192
|
+
edit = find_edit(line_num)
|
|
193
|
+
if not edit:
|
|
194
|
+
final_lines.append(line)
|
|
195
|
+
continue
|
|
196
|
+
|
|
197
|
+
if edit["applied"]:
|
|
198
|
+
continue
|
|
199
|
+
|
|
200
|
+
replace_lines = edit["replace"].split("\n")
|
|
201
|
+
final_lines.extend(replace_lines)
|
|
202
|
+
original_lines = original_content.split("\n")
|
|
203
|
+
replaced_lines = original_lines[edit["line_start"] - 1:edit["line_end"]]
|
|
204
|
+
|
|
205
|
+
prev_line_num = line_num if line_num == 1 else line_num - 1
|
|
206
|
+
line_before = "" if i == 0 else f" {original_lines[i - 1]}\n"
|
|
207
|
+
line_after = "" if i == len(original_lines) - 1 else f"\n {original_lines[i + 1]}"
|
|
208
|
+
replaced_with_minuses = "\n".join([f"-{line}" for line in replaced_lines]) if file_exists else ""
|
|
209
|
+
replace_with_pluses = "\n".join([f"+{line}" for line in edit["replace"].split("\n")])
|
|
210
|
+
diff_text = f"{diff_text}\n\n@@ -{prev_line_num},{len(replaced_lines)} +{prev_line_num},{len(replace_lines)} @@\n{line_before}{replaced_with_minuses}\n{replace_with_pluses}{line_after}"
|
|
211
|
+
edit["applied"] = True
|
|
212
|
+
|
|
157
213
|
console.print()
|
|
158
|
-
console.print(Markdown(f"```diff\
|
|
214
|
+
console.print(Markdown(f"```diff\n{diff_text}\n```"))
|
|
159
215
|
answer = console.input("\n[bold]Run?[/] ([bold]Y[/]/n): ").strip().lower()
|
|
160
216
|
if answer not in ("yes", "y", ""):
|
|
161
217
|
reason = console.input("Why not? (optional, press Enter to skip): ").strip()
|
|
@@ -163,18 +219,8 @@ class TassApp:
|
|
|
163
219
|
|
|
164
220
|
console.print(" └ Running...")
|
|
165
221
|
try:
|
|
166
|
-
with open(path, "r") as f:
|
|
167
|
-
original_content = f.read()
|
|
168
|
-
|
|
169
|
-
if find not in original_content:
|
|
170
|
-
console.print(" [red]edit_file failed[/red]")
|
|
171
|
-
console.print(f" [red]edit_file failed:\n'{find}'\nnot found in file[/red]")
|
|
172
|
-
return f"edit_file failed: '{find}' not found in file"
|
|
173
|
-
|
|
174
|
-
new_content = original_content.replace(find, replace)
|
|
175
|
-
|
|
176
222
|
with open(path, "w") as f:
|
|
177
|
-
f.write(
|
|
223
|
+
f.write("\n".join(final_lines))
|
|
178
224
|
except Exception as e:
|
|
179
225
|
console.print(" [red]edit_file failed[/red]")
|
|
180
226
|
console.print(f" [red]{str(e)}[/red]")
|
src/constants.py
CHANGED
|
@@ -37,7 +37,7 @@ TOOLS = [
|
|
|
37
37
|
"type": "function",
|
|
38
38
|
"function": {
|
|
39
39
|
"name": "edit_file",
|
|
40
|
-
"description": "Edits a file.
|
|
40
|
+
"description": "Edits (or creates) a file. Makes multiple replacements in one call. Each edit removes the contents between 'line_start' and 'line_end' inclusive and replaces it with 'replace'. If creating a file, only return a single edit where the line_start and line_end are both 1 and replace is the entire contents of the file.",
|
|
41
41
|
"parameters": {
|
|
42
42
|
"type": "object",
|
|
43
43
|
"properties": {
|
|
@@ -45,16 +45,30 @@ TOOLS = [
|
|
|
45
45
|
"type": "string",
|
|
46
46
|
"description": "Relative path of the file",
|
|
47
47
|
},
|
|
48
|
-
"
|
|
49
|
-
"type": "
|
|
50
|
-
"description": "
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
48
|
+
"edits": {
|
|
49
|
+
"type": "array",
|
|
50
|
+
"description": "List of edits to apply. Each edit must contain 'line_start', 'line_end', and 'replace'.",
|
|
51
|
+
"items": {
|
|
52
|
+
"type": "object",
|
|
53
|
+
"properties": {
|
|
54
|
+
"line_start": {
|
|
55
|
+
"type": "integer",
|
|
56
|
+
"description": "The first line to remove (inclusive)",
|
|
57
|
+
},
|
|
58
|
+
"line_end": {
|
|
59
|
+
"type": "integer",
|
|
60
|
+
"description": "The last line to remove (inclusive)",
|
|
61
|
+
},
|
|
62
|
+
"replace": {
|
|
63
|
+
"type": "string",
|
|
64
|
+
"description": "The string to replace with. Must have the correct spacing and indentation for all lines.",
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
"required": ["line_start", "line_end", "replace"],
|
|
68
|
+
},
|
|
55
69
|
},
|
|
56
70
|
},
|
|
57
|
-
"required": ["path", "
|
|
71
|
+
"required": ["path", "edits"],
|
|
58
72
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
59
73
|
},
|
|
60
74
|
},
|
|
@@ -63,7 +77,7 @@ TOOLS = [
|
|
|
63
77
|
"type": "function",
|
|
64
78
|
"function": {
|
|
65
79
|
"name": "read_file",
|
|
66
|
-
"description": "Read a file's contents (up to 1000 lines)",
|
|
80
|
+
"description": "Read a file's contents (up to 1000 lines). The output will be identical to calling `cat -n <path>` with preceding spaces, line number and a tab.",
|
|
67
81
|
"parameters": {
|
|
68
82
|
"type": "object",
|
|
69
83
|
"properties": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tass
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.6
|
|
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
|
|
@@ -33,4 +33,4 @@ tass
|
|
|
33
33
|
|
|
34
34
|
tass has only been tested with gpt-oss-120b using llama.cpp so far, but in theory any LLM with tool calling capabilities should work. By default, it will try connecting to http://localhost:8080. If you want to use another host, set the `TASS_HOST` environment variable.
|
|
35
35
|
|
|
36
|
-
Once it's running, you can ask questions like "
|
|
36
|
+
Once it's running, you can ask questions or give commands like "Create an empty file called test.txt" and it will propose a command to run after user confirmation.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
src/__init__.py,sha256=tu2q9W5_pkq30l3tRMTGahColBAAubbLP6LaB3l3IFg,89
|
|
2
|
+
src/app.py,sha256=SZGIStkRskTraOARKR-sh8hjfQT7EXwJBG-oymIABhU,11466
|
|
3
|
+
src/cli.py,sha256=op3fYcyfek_KqCCiA-Zdlc9jVZSCi036whMmR2ZjjAs,76
|
|
4
|
+
src/constants.py,sha256=2MWn3-tvZjJ2xW68BE7S1V8CgqDuBt3cBG5Bx8ILrKY,4620
|
|
5
|
+
src/utils.py,sha256=rKq34DVmFbsWPy7R6Bfdvv1ztzFLPT4hUd8BFpPHjqs,681
|
|
6
|
+
tass-0.1.6.dist-info/METADATA,sha256=xu-OHc1sIrlDrxXVCUSdPmzYnjVam5tYB96EYJiTWCc,1079
|
|
7
|
+
tass-0.1.6.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
8
|
+
tass-0.1.6.dist-info/entry_points.txt,sha256=pviKuIOuHvaQ7_YiFxatJEY8XYfh3EzVWy4LJh0v-A0,38
|
|
9
|
+
tass-0.1.6.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
10
|
+
tass-0.1.6.dist-info/RECORD,,
|
tass-0.1.4.dist-info/RECORD
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
src/__init__.py,sha256=tu2q9W5_pkq30l3tRMTGahColBAAubbLP6LaB3l3IFg,89
|
|
2
|
-
src/app.py,sha256=sTPTwDc8KjF9RpL7W5Ix0D0n8ERcWsP1WEVzLWGOKco,9779
|
|
3
|
-
src/cli.py,sha256=op3fYcyfek_KqCCiA-Zdlc9jVZSCi036whMmR2ZjjAs,76
|
|
4
|
-
src/constants.py,sha256=A0_PwEYo1Dpf8UC6HV7JgQwU7GlZQxKhLgwgmyoc3WY,3478
|
|
5
|
-
src/utils.py,sha256=rKq34DVmFbsWPy7R6Bfdvv1ztzFLPT4hUd8BFpPHjqs,681
|
|
6
|
-
tass-0.1.4.dist-info/METADATA,sha256=K1hq1AlWam-s671Nhmb0-X4jj6qAOi8Lfw27wuDxAXA,1071
|
|
7
|
-
tass-0.1.4.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
8
|
-
tass-0.1.4.dist-info/entry_points.txt,sha256=pviKuIOuHvaQ7_YiFxatJEY8XYfh3EzVWy4LJh0v-A0,38
|
|
9
|
-
tass-0.1.4.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
10
|
-
tass-0.1.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|