tass 0.1.7__tar.gz → 0.1.9__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.4
2
2
  Name: tass
3
- Version: 0.1.7
3
+ Version: 0.1.9
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
@@ -13,6 +13,10 @@ Description-Content-Type: text/markdown
13
13
 
14
14
  # tass
15
15
 
16
+ <p align="center">
17
+ <img src="assets/tass.gif" alt="Demo" />
18
+ </p>
19
+
16
20
  A terminal assistant that allows you to ask an LLM to run commands.
17
21
 
18
22
  ## Warning
@@ -21,16 +25,40 @@ This tool can run commands including ones that can modify, move, or delete files
21
25
 
22
26
  ## Installation
23
27
 
28
+ ### Using uv
29
+
24
30
  ```
25
31
  uv tool install tass
26
32
  ```
27
33
 
34
+ ### Using pip
35
+
36
+ ```
37
+ pip install tass
38
+ ```
39
+
28
40
  You can run it with
29
41
 
30
42
  ```
31
43
  tass
32
44
  ```
33
45
 
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.
46
+ 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.
35
47
 
36
48
  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.
49
+
50
+ You can enter multiline input by ending lines with a backslash (\\). The continuation prompt will appear until you enter a line without a trailing backslash.
51
+
52
+ ## Upgrade
53
+
54
+ ### Using uv
55
+
56
+ ```
57
+ uv tool upgrade tass
58
+ ```
59
+
60
+ ### Using pip
61
+
62
+ ```
63
+ pip install --upgrade tass
64
+ ```
@@ -1,5 +1,9 @@
1
1
  # tass
2
2
 
3
+ <p align="center">
4
+ <img src="assets/tass.gif" alt="Demo" />
5
+ </p>
6
+
3
7
  A terminal assistant that allows you to ask an LLM to run commands.
4
8
 
5
9
  ## Warning
@@ -8,16 +12,40 @@ This tool can run commands including ones that can modify, move, or delete files
8
12
 
9
13
  ## Installation
10
14
 
15
+ ### Using uv
16
+
11
17
  ```
12
18
  uv tool install tass
13
19
  ```
14
20
 
21
+ ### Using pip
22
+
23
+ ```
24
+ pip install tass
25
+ ```
26
+
15
27
  You can run it with
16
28
 
17
29
  ```
18
30
  tass
19
31
  ```
20
32
 
21
- 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.
33
+ 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.
22
34
 
23
35
  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.
36
+
37
+ You can enter multiline input by ending lines with a backslash (\\). The continuation prompt will appear until you enter a line without a trailing backslash.
38
+
39
+ ## Upgrade
40
+
41
+ ### Using uv
42
+
43
+ ```
44
+ uv tool upgrade tass
45
+ ```
46
+
47
+ ### Using pip
48
+
49
+ ```
50
+ pip install --upgrade tass
51
+ ```
Binary file
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tass"
3
- version = "0.1.7"
3
+ version = "0.1.9"
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"
@@ -15,9 +15,7 @@ from src.constants import (
15
15
  SYSTEM_PROMPT,
16
16
  TOOLS,
17
17
  )
18
- from src.utils import (
19
- is_read_only_command,
20
- )
18
+ from src.utils import is_read_only_command
21
19
 
22
20
  console = Console()
23
21
 
@@ -66,11 +64,14 @@ class TassApp:
66
64
 
67
65
  prompt = (
68
66
  "The conversation is becoming long and might soon go beyond the "
69
- "context limit. Please provide a concise summary of the conversation, "
70
- "preserving all important details. Keep the summary short enough "
71
- "to fit within a few paragraphs at the most."
67
+ "context limit. Please provide a detailed summary of the conversation, "
68
+ "preserving all important details. Make sure context is not lost so that "
69
+ "the conversation can continue without needing to reclarify anything. "
70
+ "You don't have to preserve entire contents of files that have been read "
71
+ " or edited, they can be read again if necessary."
72
72
  )
73
73
 
74
+ console.print("\n - Summarizing conversation...")
74
75
  response = requests.post(
75
76
  f"{self.host}/v1/chat/completions",
76
77
  json={
@@ -84,6 +85,7 @@ class TassApp:
84
85
  data = response.json()
85
86
  summary = data["choices"][0]["message"]["content"]
86
87
  self.messages = [self.messages[0], {"role": "assistant", "content": f"Summary of the conversation so far:\n{summary}"}]
88
+ console.print(" [green]Summarization completed[/green]")
87
89
 
88
90
  def call_llm(self) -> bool:
89
91
  response = requests.post(
@@ -102,22 +104,34 @@ class TassApp:
102
104
  content = ""
103
105
  reasoning_content = ""
104
106
  tool_calls_map = {}
107
+ timings_str = ""
105
108
 
106
- def generate_layout(reasoning_content: str, content: str):
109
+ def generate_layout():
107
110
  groups = []
108
111
 
109
112
  if reasoning_content:
113
+ last_three_lines = "\n".join(reasoning_content.rstrip().split("\n")[-3:])
110
114
  groups.append(Text(""))
111
- groups.append(Panel(Text(reasoning_content, style="grey50"), title="Thought process", title_align="left", style="grey50"))
115
+ groups.append(
116
+ Panel(
117
+ Text(
118
+ last_three_lines,
119
+ style="grey50",
120
+ ),
121
+ title="Thought process",
122
+ title_align="left",
123
+ subtitle=timings_str,
124
+ style="grey50",
125
+ )
126
+ )
112
127
 
113
128
  if content:
114
129
  groups.append(Text(""))
115
- groups.append(Markdown(content))
116
- groups.append(Text(""))
130
+ groups.append(Markdown(content.rstrip()))
117
131
 
118
132
  return Group(*groups)
119
133
 
120
- with Live(generate_layout(reasoning_content, content), refresh_per_second=10) as live:
134
+ with Live(generate_layout(), refresh_per_second=10) as live:
121
135
  for line in response.iter_lines():
122
136
  line = line.decode("utf-8")
123
137
  if not line.strip():
@@ -127,17 +141,29 @@ class TassApp:
127
141
  continue
128
142
 
129
143
  chunk = json.loads(line.removeprefix("data:"))
144
+ if all(k in chunk.get("timings", {}) for k in ["prompt_n", "prompt_per_second", "predicted_n", "predicted_per_second"]):
145
+ timings = chunk["timings"]
146
+ timings_str = (
147
+ f"Input: {timings['prompt_n']:,} tokens, {timings['prompt_per_second']:,.2f} tok/s | "
148
+ f"Output: {timings['predicted_n']:,} tokens, {timings['predicted_per_second']:,.2f} tok/s"
149
+ )
150
+
151
+ if chunk["choices"][0]["finish_reason"]:
152
+ live.update(generate_layout())
153
+
130
154
  delta = chunk["choices"][0]["delta"]
155
+ if not any([delta.get(key) for key in ["content", "reasoning_content", "tool_calls"]]):
156
+ continue
157
+
158
+ if delta.get("reasoning_content"):
159
+ reasoning_content += delta["reasoning_content"]
160
+ live.update(generate_layout())
161
+
131
162
  if delta.get("content"):
132
163
  content += delta["content"]
133
- last_three_lines = "\n".join(reasoning_content.rstrip().split("\n")[-3:])
134
- live.update(generate_layout(last_three_lines, content.rstrip()))
135
- if delta.get("reasoning_content" ):
136
- reasoning_content += delta["reasoning_content"]
137
- last_three_lines = "\n".join(reasoning_content.rstrip().split("\n")[-3:])
138
- live.update(generate_layout(last_three_lines, content.rstrip()))
164
+ live.update(generate_layout())
139
165
 
140
- for tool_call_delta in delta.get("tool_calls", []):
166
+ for tool_call_delta in delta.get("tool_calls") or []:
141
167
  index = tool_call_delta["index"]
142
168
  if index not in tool_calls_map:
143
169
  tool_calls_map[index] = (
@@ -164,16 +190,12 @@ class TassApp:
164
190
  if function.get("arguments"):
165
191
  tool_call["function"]["arguments"] += function["arguments"]
166
192
 
167
- if chunk["choices"][0]["finish_reason"]:
168
- last_three_lines = "\n".join(reasoning_content.rstrip().split("\n")[-3:])
169
- live.update(generate_layout(last_three_lines, content.rstrip()))
170
-
171
193
  self.messages.append(
172
194
  {
173
195
  "role": "assistant",
174
- "content": content,
175
- "reasoning_content": reasoning_content,
176
- "tool_calls": list(tool_calls_map.values()),
196
+ "content": content.strip() or None,
197
+ "reasoning_content": reasoning_content.strip() or None,
198
+ "tool_calls": list(tool_calls_map.values()) or None,
177
199
  }
178
200
  )
179
201
 
@@ -362,14 +384,22 @@ class TassApp:
362
384
  def run(self):
363
385
  try:
364
386
  self._check_llm_host()
365
- console.print()
366
387
  except KeyboardInterrupt:
367
388
  console.print("\nBye!")
368
389
  return
369
390
 
370
391
  while True:
392
+ console.print()
371
393
  try:
372
- user_input = console.input("> ").strip()
394
+ input_lines = []
395
+ while True:
396
+ input_line = console.input("> ")
397
+ if not input_line or input_line[-1] != "\\":
398
+ input_lines.append(input_line)
399
+ break
400
+ input_lines.append(input_line[:-1])
401
+
402
+ user_input = "\n".join(input_lines)
373
403
  except KeyboardInterrupt:
374
404
  console.print("\nBye!")
375
405
  break
@@ -113,4 +113,5 @@ READ_ONLY_COMMANDS = [
113
113
  "which",
114
114
  "sed",
115
115
  "find",
116
+ "test",
116
117
  ]
@@ -261,7 +261,7 @@ wheels = [
261
261
 
262
262
  [[package]]
263
263
  name = "tass"
264
- version = "0.1.7"
264
+ version = "0.1.9"
265
265
  source = { editable = "." }
266
266
  dependencies = [
267
267
  { name = "requests" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes