tass 0.1.8__py3-none-any.whl → 0.1.10__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 +56 -33
- src/constants.py +14 -8
- src/utils.py +76 -1
- {tass-0.1.8.dist-info → tass-0.1.10.dist-info}/METADATA +7 -2
- tass-0.1.10.dist-info/RECORD +10 -0
- tass-0.1.8.dist-info/RECORD +0 -10
- {tass-0.1.8.dist-info → tass-0.1.10.dist-info}/WHEEL +0 -0
- {tass-0.1.8.dist-info → tass-0.1.10.dist-info}/entry_points.txt +0 -0
- {tass-0.1.8.dist-info → tass-0.1.10.dist-info}/licenses/LICENSE +0 -0
src/app.py
CHANGED
|
@@ -4,7 +4,7 @@ import subprocess
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
|
|
6
6
|
import requests
|
|
7
|
-
|
|
7
|
+
from prompt_toolkit import prompt
|
|
8
8
|
from rich.console import Console, Group
|
|
9
9
|
from rich.live import Live
|
|
10
10
|
from rich.markdown import Markdown
|
|
@@ -15,7 +15,11 @@ from src.constants import (
|
|
|
15
15
|
SYSTEM_PROMPT,
|
|
16
16
|
TOOLS,
|
|
17
17
|
)
|
|
18
|
-
from src.utils import
|
|
18
|
+
from src.utils import (
|
|
19
|
+
FileCompleter,
|
|
20
|
+
create_key_bindings,
|
|
21
|
+
is_read_only_command,
|
|
22
|
+
)
|
|
19
23
|
|
|
20
24
|
console = Console()
|
|
21
25
|
|
|
@@ -25,6 +29,8 @@ class TassApp:
|
|
|
25
29
|
def __init__(self):
|
|
26
30
|
self.messages: list[dict] = [{"role": "system", "content": SYSTEM_PROMPT}]
|
|
27
31
|
self.host = os.environ.get("TASS_HOST", "http://localhost:8080")
|
|
32
|
+
self.key_bindings = create_key_bindings()
|
|
33
|
+
self.file_completer = FileCompleter()
|
|
28
34
|
self.TOOLS_MAP = {
|
|
29
35
|
"execute": self.execute,
|
|
30
36
|
"read_file": self.read_file,
|
|
@@ -64,11 +70,14 @@ class TassApp:
|
|
|
64
70
|
|
|
65
71
|
prompt = (
|
|
66
72
|
"The conversation is becoming long and might soon go beyond the "
|
|
67
|
-
"context limit. Please provide a
|
|
68
|
-
"preserving all important details.
|
|
69
|
-
"
|
|
73
|
+
"context limit. Please provide a detailed summary of the conversation, "
|
|
74
|
+
"preserving all important details. Make sure context is not lost so that "
|
|
75
|
+
"the conversation can continue without needing to reclarify anything. "
|
|
76
|
+
"You don't have to preserve entire contents of files that have been read "
|
|
77
|
+
" or edited, they can be read again if necessary."
|
|
70
78
|
)
|
|
71
79
|
|
|
80
|
+
console.print("\n - Summarizing conversation...")
|
|
72
81
|
response = requests.post(
|
|
73
82
|
f"{self.host}/v1/chat/completions",
|
|
74
83
|
json={
|
|
@@ -82,6 +91,7 @@ class TassApp:
|
|
|
82
91
|
data = response.json()
|
|
83
92
|
summary = data["choices"][0]["message"]["content"]
|
|
84
93
|
self.messages = [self.messages[0], {"role": "assistant", "content": f"Summary of the conversation so far:\n{summary}"}]
|
|
94
|
+
console.print(" [green]Summarization completed[/green]")
|
|
85
95
|
|
|
86
96
|
def call_llm(self) -> bool:
|
|
87
97
|
response = requests.post(
|
|
@@ -137,15 +147,29 @@ class TassApp:
|
|
|
137
147
|
continue
|
|
138
148
|
|
|
139
149
|
chunk = json.loads(line.removeprefix("data:"))
|
|
150
|
+
if all(k in chunk.get("timings", {}) for k in ["prompt_n", "prompt_per_second", "predicted_n", "predicted_per_second"]):
|
|
151
|
+
timings = chunk["timings"]
|
|
152
|
+
timings_str = (
|
|
153
|
+
f"Input: {timings['prompt_n']:,} tokens, {timings['prompt_per_second']:,.2f} tok/s | "
|
|
154
|
+
f"Output: {timings['predicted_n']:,} tokens, {timings['predicted_per_second']:,.2f} tok/s"
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
if chunk["choices"][0]["finish_reason"]:
|
|
158
|
+
live.update(generate_layout())
|
|
159
|
+
|
|
140
160
|
delta = chunk["choices"][0]["delta"]
|
|
161
|
+
if not any([delta.get(key) for key in ["content", "reasoning_content", "tool_calls"]]):
|
|
162
|
+
continue
|
|
163
|
+
|
|
164
|
+
if delta.get("reasoning_content"):
|
|
165
|
+
reasoning_content += delta["reasoning_content"]
|
|
166
|
+
live.update(generate_layout())
|
|
167
|
+
|
|
141
168
|
if delta.get("content"):
|
|
142
169
|
content += delta["content"]
|
|
143
170
|
live.update(generate_layout())
|
|
144
|
-
if delta.get("reasoning_content" ):
|
|
145
|
-
reasoning_content += delta["reasoning_content"]
|
|
146
|
-
live.update(generate_layout())
|
|
147
171
|
|
|
148
|
-
for tool_call_delta in delta.get("tool_calls"
|
|
172
|
+
for tool_call_delta in delta.get("tool_calls") or []:
|
|
149
173
|
index = tool_call_delta["index"]
|
|
150
174
|
if index not in tool_calls_map:
|
|
151
175
|
tool_calls_map[index] = (
|
|
@@ -172,22 +196,12 @@ class TassApp:
|
|
|
172
196
|
if function.get("arguments"):
|
|
173
197
|
tool_call["function"]["arguments"] += function["arguments"]
|
|
174
198
|
|
|
175
|
-
if all(k in chunk.get("timings", {}) for k in ["prompt_n", "prompt_per_second", "predicted_n", "predicted_per_second"]):
|
|
176
|
-
timings = chunk["timings"]
|
|
177
|
-
timings_str = (
|
|
178
|
-
f"Input: {timings['prompt_n']:,} tokens, {timings['prompt_per_second']:,.2f} tok/s | "
|
|
179
|
-
f"Output: {timings['predicted_n']:,} tokens, {timings['predicted_per_second']:,.2f} tok/s"
|
|
180
|
-
)
|
|
181
|
-
|
|
182
|
-
if chunk["choices"][0]["finish_reason"]:
|
|
183
|
-
live.update(generate_layout())
|
|
184
|
-
|
|
185
199
|
self.messages.append(
|
|
186
200
|
{
|
|
187
201
|
"role": "assistant",
|
|
188
|
-
"content": content,
|
|
189
|
-
"reasoning_content": reasoning_content,
|
|
190
|
-
"tool_calls": list(tool_calls_map.values()),
|
|
202
|
+
"content": content.strip(),
|
|
203
|
+
"reasoning_content": reasoning_content.strip(),
|
|
204
|
+
"tool_calls": list(tool_calls_map.values()) or [],
|
|
191
205
|
}
|
|
192
206
|
)
|
|
193
207
|
|
|
@@ -212,11 +226,12 @@ class TassApp:
|
|
|
212
226
|
self.messages.append({"role": "user", "content": str(e)})
|
|
213
227
|
return self.call_llm()
|
|
214
228
|
|
|
215
|
-
def read_file(self, path: str, start: int = 1) -> str:
|
|
216
|
-
if start == 1:
|
|
229
|
+
def read_file(self, path: str, start: int = 1, num_lines: int = 1000) -> str:
|
|
230
|
+
if start == 1 and num_lines == 1000:
|
|
217
231
|
console.print(f" └ Reading file [bold]{path}[/]...")
|
|
218
232
|
else:
|
|
219
|
-
|
|
233
|
+
last_line = start + num_lines - 1
|
|
234
|
+
console.print(f" └ Reading file [bold]{path}[/] (lines {start}-{last_line})...")
|
|
220
235
|
|
|
221
236
|
try:
|
|
222
237
|
result = subprocess.run(
|
|
@@ -248,14 +263,15 @@ class TassApp:
|
|
|
248
263
|
lines.append(line)
|
|
249
264
|
line_num += 1
|
|
250
265
|
|
|
251
|
-
if len(lines) >=
|
|
266
|
+
if len(lines) >= num_lines:
|
|
252
267
|
lines.append("... (truncated)")
|
|
253
268
|
break
|
|
254
269
|
|
|
255
270
|
console.print(" [green]Command succeeded[/green]")
|
|
256
|
-
return "".join(lines)
|
|
271
|
+
return "\n".join(lines)
|
|
257
272
|
|
|
258
273
|
def edit_file(self, path: str, edits: list[dict]) -> str:
|
|
274
|
+
console.print(json.dumps(edits, indent=2))
|
|
259
275
|
for edit in edits:
|
|
260
276
|
edit["applied"] = False
|
|
261
277
|
|
|
@@ -286,8 +302,9 @@ class TassApp:
|
|
|
286
302
|
if edit["applied"]:
|
|
287
303
|
continue
|
|
288
304
|
|
|
289
|
-
replace_lines = edit["
|
|
290
|
-
|
|
305
|
+
replace_lines = edit["content"].split("\n")
|
|
306
|
+
if edit["content"]:
|
|
307
|
+
final_lines.extend(replace_lines)
|
|
291
308
|
original_lines = original_content.split("\n")
|
|
292
309
|
replaced_lines = original_lines[edit["line_start"] - 1:edit["line_end"]]
|
|
293
310
|
|
|
@@ -295,8 +312,10 @@ class TassApp:
|
|
|
295
312
|
line_before = "" if i == 0 else f" {original_lines[i - 1]}\n"
|
|
296
313
|
line_after = "" if edit["line_end"] == len(original_lines) else f"\n {original_lines[edit['line_end']]}"
|
|
297
314
|
replaced_with_minuses = "\n".join([f"-{line}" for line in replaced_lines]) if file_exists else ""
|
|
298
|
-
|
|
299
|
-
|
|
315
|
+
replaced_with_pluses = ""
|
|
316
|
+
if edit["content"]:
|
|
317
|
+
replaced_with_pluses = "\n" + "\n".join([f"+{line}" for line in edit["content"].split("\n")])
|
|
318
|
+
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}{replaced_with_pluses}{line_after}"
|
|
300
319
|
edit["applied"] = True
|
|
301
320
|
|
|
302
321
|
console.print()
|
|
@@ -385,12 +404,16 @@ class TassApp:
|
|
|
385
404
|
try:
|
|
386
405
|
input_lines = []
|
|
387
406
|
while True:
|
|
388
|
-
input_line =
|
|
407
|
+
input_line = prompt(
|
|
408
|
+
"> ",
|
|
409
|
+
completer=self.file_completer,
|
|
410
|
+
complete_while_typing=True,
|
|
411
|
+
key_bindings=self.key_bindings,
|
|
412
|
+
)
|
|
389
413
|
if not input_line or input_line[-1] != "\\":
|
|
390
414
|
input_lines.append(input_line)
|
|
391
415
|
break
|
|
392
416
|
input_lines.append(input_line[:-1])
|
|
393
|
-
|
|
394
417
|
user_input = "\n".join(input_lines)
|
|
395
418
|
except KeyboardInterrupt:
|
|
396
419
|
console.print("\nBye!")
|
src/constants.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
cwd_path = Path.cwd().resolve()
|
|
4
4
|
|
|
5
5
|
SYSTEM_PROMPT = f"""You are tass, or Terminal Assistant, a helpful AI that executes shell commands based on natural-language requests.
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@ If the user's request involves making changes to the filesystem such as creating
|
|
|
8
8
|
|
|
9
9
|
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.
|
|
10
10
|
|
|
11
|
-
Current working directory: {
|
|
11
|
+
Current working directory: {cwd_path}"""
|
|
12
12
|
|
|
13
13
|
TOOLS = [
|
|
14
14
|
{
|
|
@@ -37,7 +37,7 @@ TOOLS = [
|
|
|
37
37
|
"type": "function",
|
|
38
38
|
"function": {
|
|
39
39
|
"name": "edit_file",
|
|
40
|
-
"description": "Edits (or creates) a file.
|
|
40
|
+
"description": "Edits (or creates) a file. Can make multiple edits in one call. Each edit replaces the contents between 'line_start' and 'line_end' inclusive with 'content'. If creating a file, only return a single edit where 'line_start' and 'line_end' are both 1 and 'content' is the entire contents of the file. You must use the read_file tool before editing a file.",
|
|
41
41
|
"parameters": {
|
|
42
42
|
"type": "object",
|
|
43
43
|
"properties": {
|
|
@@ -47,7 +47,7 @@ TOOLS = [
|
|
|
47
47
|
},
|
|
48
48
|
"edits": {
|
|
49
49
|
"type": "array",
|
|
50
|
-
"description": "List of edits to apply. Each edit must contain 'line_start', 'line_end', and '
|
|
50
|
+
"description": "List of edits to apply. Each edit must contain 'line_start', 'line_end', and 'content'.",
|
|
51
51
|
"items": {
|
|
52
52
|
"type": "object",
|
|
53
53
|
"properties": {
|
|
@@ -59,12 +59,12 @@ TOOLS = [
|
|
|
59
59
|
"type": "integer",
|
|
60
60
|
"description": "The last line to remove (inclusive)",
|
|
61
61
|
},
|
|
62
|
-
"
|
|
62
|
+
"content": {
|
|
63
63
|
"type": "string",
|
|
64
|
-
"description": "The
|
|
64
|
+
"description": "The content to replace with. Must have the correct spacing and indentation for all lines.",
|
|
65
65
|
},
|
|
66
66
|
},
|
|
67
|
-
"required": ["line_start", "line_end", "
|
|
67
|
+
"required": ["line_start", "line_end", "content"],
|
|
68
68
|
},
|
|
69
69
|
},
|
|
70
70
|
},
|
|
@@ -77,7 +77,7 @@ TOOLS = [
|
|
|
77
77
|
"type": "function",
|
|
78
78
|
"function": {
|
|
79
79
|
"name": "read_file",
|
|
80
|
-
"description": "Read a file's contents (
|
|
80
|
+
"description": "Read a file's contents (the first 1000 lines by default). When reading a file for the first time, do not change the defaults and always read the first 1000 lines unless you are absolutely certain of which lines need to be read. The output will be identical to calling `cat -n <path>` with preceding spaces, line number and a tab.",
|
|
81
81
|
"parameters": {
|
|
82
82
|
"type": "object",
|
|
83
83
|
"properties": {
|
|
@@ -90,6 +90,11 @@ TOOLS = [
|
|
|
90
90
|
"description": "Which line to start reading from",
|
|
91
91
|
"default": 1,
|
|
92
92
|
},
|
|
93
|
+
"num_lines": {
|
|
94
|
+
"type": "integer",
|
|
95
|
+
"description": "Number of lines to read, defaults to 1000",
|
|
96
|
+
"default": 1000,
|
|
97
|
+
},
|
|
93
98
|
},
|
|
94
99
|
"required": ["path"],
|
|
95
100
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
@@ -113,4 +118,5 @@ READ_ONLY_COMMANDS = [
|
|
|
113
118
|
"which",
|
|
114
119
|
"sed",
|
|
115
120
|
"find",
|
|
121
|
+
"test",
|
|
116
122
|
]
|
src/utils.py
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
|
-
from
|
|
1
|
+
from prompt_toolkit.completion import (
|
|
2
|
+
Completer,
|
|
3
|
+
Completion,
|
|
4
|
+
CompleteEvent,
|
|
5
|
+
)
|
|
6
|
+
from prompt_toolkit.document import Document
|
|
7
|
+
from prompt_toolkit.key_binding import KeyBindings
|
|
8
|
+
from prompt_toolkit.keys import Keys
|
|
2
9
|
|
|
10
|
+
from src.constants import (
|
|
11
|
+
READ_ONLY_COMMANDS,
|
|
12
|
+
cwd_path,
|
|
13
|
+
)
|
|
3
14
|
|
|
4
15
|
def is_read_only_command(command: str) -> bool:
|
|
5
16
|
"""A simple check to see if the command is only for reading files.
|
|
@@ -21,3 +32,67 @@ def is_read_only_command(command: str) -> bool:
|
|
|
21
32
|
return False
|
|
22
33
|
|
|
23
34
|
return True
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class FileCompleter(Completer):
|
|
38
|
+
|
|
39
|
+
def get_completions(self, document: Document, complete_event: CompleteEvent) -> list[Completion]:
|
|
40
|
+
"""Return file completions for text after @ anywhere in input."""
|
|
41
|
+
text = document.text
|
|
42
|
+
cursor_pos = document.cursor_position
|
|
43
|
+
text_before_cursor = document.text_before_cursor
|
|
44
|
+
last_at_pos = text_before_cursor.rfind("@")
|
|
45
|
+
if last_at_pos == -1:
|
|
46
|
+
return []
|
|
47
|
+
|
|
48
|
+
text_after_at = text[last_at_pos + 1 : cursor_pos]
|
|
49
|
+
|
|
50
|
+
base_dir = cwd_path
|
|
51
|
+
search_pattern = text_after_at
|
|
52
|
+
prefix = ""
|
|
53
|
+
|
|
54
|
+
if "/" in text_after_at:
|
|
55
|
+
dir_part, file_part = text_after_at.rsplit("/", 1)
|
|
56
|
+
base_dir = cwd_path / dir_part
|
|
57
|
+
search_pattern = file_part
|
|
58
|
+
prefix = dir_part + "/"
|
|
59
|
+
|
|
60
|
+
if not base_dir.is_dir():
|
|
61
|
+
return []
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
dirs = []
|
|
65
|
+
files = []
|
|
66
|
+
for entry in sorted(base_dir.iterdir()):
|
|
67
|
+
name = entry.name
|
|
68
|
+
if search_pattern in name:
|
|
69
|
+
full_path = prefix + name
|
|
70
|
+
if entry.is_dir():
|
|
71
|
+
dirs.append(Completion(full_path, display=name, start_position=-len(text_after_at), style="Blue"))
|
|
72
|
+
else:
|
|
73
|
+
files.append(Completion(full_path, display=name, start_position=-len(text_after_at)))
|
|
74
|
+
|
|
75
|
+
return dirs + files
|
|
76
|
+
except (PermissionError, OSError):
|
|
77
|
+
return []
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def create_key_bindings() -> KeyBindings:
|
|
81
|
+
bindings = KeyBindings()
|
|
82
|
+
|
|
83
|
+
@bindings.add(Keys.Backspace)
|
|
84
|
+
def backspace_with_completion(event):
|
|
85
|
+
"""Also trigger completion on backspace."""
|
|
86
|
+
buffer = event.current_buffer
|
|
87
|
+
buffer.delete_before_cursor(count=1)
|
|
88
|
+
text_before_cursor = buffer.document.text_before_cursor
|
|
89
|
+
last_at_pos = text_before_cursor.rfind("@")
|
|
90
|
+
if last_at_pos != -1:
|
|
91
|
+
buffer.start_completion()
|
|
92
|
+
|
|
93
|
+
@bindings.add(Keys.ControlC)
|
|
94
|
+
def ctrl_c_handler(event):
|
|
95
|
+
"""Ctrl+C should raise KeyboardInterrupt."""
|
|
96
|
+
raise KeyboardInterrupt()
|
|
97
|
+
|
|
98
|
+
return bindings
|
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tass
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.10
|
|
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
|
|
7
7
|
License: MIT
|
|
8
8
|
License-File: LICENSE
|
|
9
9
|
Requires-Python: >=3.10
|
|
10
|
+
Requires-Dist: prompt-toolkit>=3.0.52
|
|
10
11
|
Requires-Dist: requests>=2.32.5
|
|
11
12
|
Requires-Dist: rich>=14.2.0
|
|
12
13
|
Description-Content-Type: text/markdown
|
|
13
14
|
|
|
14
15
|
# tass
|
|
15
16
|
|
|
17
|
+
<p align="center">
|
|
18
|
+
<img src="assets/tass.gif" alt="Demo" />
|
|
19
|
+
</p>
|
|
20
|
+
|
|
16
21
|
A terminal assistant that allows you to ask an LLM to run commands.
|
|
17
22
|
|
|
18
23
|
## Warning
|
|
@@ -39,7 +44,7 @@ You can run it with
|
|
|
39
44
|
tass
|
|
40
45
|
```
|
|
41
46
|
|
|
42
|
-
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.
|
|
47
|
+
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. At the moment there's no support for connecting tass to a non-local API, nor are there plans for it. For the time being, I plan on keeping tass completely local. There's no telemetry, no logs, just a simple REPL loop.
|
|
43
48
|
|
|
44
49
|
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.
|
|
45
50
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
src/__init__.py,sha256=tu2q9W5_pkq30l3tRMTGahColBAAubbLP6LaB3l3IFg,89
|
|
2
|
+
src/app.py,sha256=45r-M59vjJb1TXdkfSkTVJq1waLyNYVLxkafav5t6oI,16472
|
|
3
|
+
src/cli.py,sha256=op3fYcyfek_KqCCiA-Zdlc9jVZSCi036whMmR2ZjjAs,76
|
|
4
|
+
src/constants.py,sha256=LYNON4xoqCssh4wA7rjkjyY2JhDXUPFmkQlTz_N0oz8,5089
|
|
5
|
+
src/utils.py,sha256=Uoi60eko9ivvnF-se68yAuwUDFEOHb9Y2SepDaOAbTU,3069
|
|
6
|
+
tass-0.1.10.dist-info/METADATA,sha256=xG6XaXmXrLhDE1uo06tK1nXaSWYz2W8nA-Yx2-ZACHA,1717
|
|
7
|
+
tass-0.1.10.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
8
|
+
tass-0.1.10.dist-info/entry_points.txt,sha256=pviKuIOuHvaQ7_YiFxatJEY8XYfh3EzVWy4LJh0v-A0,38
|
|
9
|
+
tass-0.1.10.dist-info/licenses/LICENSE,sha256=Cdr-_YJHgGaf2vJjcoOsRJySkDaogUhu3yIDvpz7GEQ,1066
|
|
10
|
+
tass-0.1.10.dist-info/RECORD,,
|
tass-0.1.8.dist-info/RECORD
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
src/__init__.py,sha256=tu2q9W5_pkq30l3tRMTGahColBAAubbLP6LaB3l3IFg,89
|
|
2
|
-
src/app.py,sha256=_ukqGrJdaaaZJiGM_XLNL7tZr2SzI_A-zlLRJrRI52I,15346
|
|
3
|
-
src/cli.py,sha256=op3fYcyfek_KqCCiA-Zdlc9jVZSCi036whMmR2ZjjAs,76
|
|
4
|
-
src/constants.py,sha256=pzriopu167r3yOOcnC80sMjPKEZoDmYV8e8i5aK0rvM,4629
|
|
5
|
-
src/utils.py,sha256=rKq34DVmFbsWPy7R6Bfdvv1ztzFLPT4hUd8BFpPHjqs,681
|
|
6
|
-
tass-0.1.8.dist-info/METADATA,sha256=w9zw9A2ARBv09FQpL-I2lRpbUd7PSGpeT51mhYIEksU,1392
|
|
7
|
-
tass-0.1.8.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
8
|
-
tass-0.1.8.dist-info/entry_points.txt,sha256=pviKuIOuHvaQ7_YiFxatJEY8XYfh3EzVWy4LJh0v-A0,38
|
|
9
|
-
tass-0.1.8.dist-info/licenses/LICENSE,sha256=Cdr-_YJHgGaf2vJjcoOsRJySkDaogUhu3yIDvpz7GEQ,1066
|
|
10
|
-
tass-0.1.8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|