cobalt-cli-linux 0.1.1__tar.gz → 0.1.2__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.
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.2}/PKG-INFO +3 -1
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.2}/README.md +2 -0
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.2}/pyproject.toml +1 -1
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.2}/src/cobalt_cli_linux/tui.py +65 -18
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.2}/src/cobalt_cli_linux.egg-info/PKG-INFO +3 -1
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.2}/tests/test_cli.py +4 -2
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.2}/setup.cfg +0 -0
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.2}/src/cobalt_cli_linux/ASCII.txt +0 -0
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.2}/src/cobalt_cli_linux/__init__.py +0 -0
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.2}/src/cobalt_cli_linux/__main__.py +0 -0
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.2}/src/cobalt_cli_linux/agent.py +0 -0
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.2}/src/cobalt_cli_linux/cli.py +0 -0
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.2}/src/cobalt_cli_linux/config.py +0 -0
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.2}/src/cobalt_cli_linux/deepseek_client.py +0 -0
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.2}/src/cobalt_cli_linux/executor.py +0 -0
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.2}/src/cobalt_cli_linux/groq_client.py +0 -0
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.2}/src/cobalt_cli_linux/history.py +0 -0
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.2}/src/cobalt_cli_linux.egg-info/SOURCES.txt +0 -0
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.2}/src/cobalt_cli_linux.egg-info/dependency_links.txt +0 -0
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.2}/src/cobalt_cli_linux.egg-info/entry_points.txt +0 -0
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.2}/src/cobalt_cli_linux.egg-info/requires.txt +0 -0
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.2}/src/cobalt_cli_linux.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cobalt-cli-linux
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Agentic Groq-powered CLI assistant for Debian Linux
|
|
5
5
|
Author: Cobalt
|
|
6
6
|
License-Expression: MIT
|
|
@@ -77,6 +77,8 @@ cobalt --interactive
|
|
|
77
77
|
|
|
78
78
|
This launches a fullscreen TUI that feels similar to a professional agent console, with the message stream and a compact prompt area instead of a raw shell prompt.
|
|
79
79
|
|
|
80
|
+
In the interface, press `Escape` to unfocus the prompt. While unfocused, `n` starts a new chat and `d` deletes the selected chat; click the prompt or type a character to focus it again. Long prompts wrap inside the prompt box.
|
|
81
|
+
|
|
80
82
|
You can also pass arguments directly:
|
|
81
83
|
|
|
82
84
|
```bash
|
|
@@ -52,6 +52,8 @@ cobalt --interactive
|
|
|
52
52
|
|
|
53
53
|
This launches a fullscreen TUI that feels similar to a professional agent console, with the message stream and a compact prompt area instead of a raw shell prompt.
|
|
54
54
|
|
|
55
|
+
In the interface, press `Escape` to unfocus the prompt. While unfocused, `n` starts a new chat and `d` deletes the selected chat; click the prompt or type a character to focus it again. Long prompts wrap inside the prompt box.
|
|
56
|
+
|
|
55
57
|
You can also pass arguments directly:
|
|
56
58
|
|
|
57
59
|
```bash
|
|
@@ -23,6 +23,7 @@ class CobaltTUI:
|
|
|
23
23
|
self.history = self.current_chat
|
|
24
24
|
self.saved_chats = self.chat_files
|
|
25
25
|
self.ascii_lines = self._load_ascii_art()
|
|
26
|
+
self.input_focused = True
|
|
26
27
|
|
|
27
28
|
def _load_ascii_art(self) -> list[str]:
|
|
28
29
|
ascii_path = Path(__file__).with_name("ASCII.txt")
|
|
@@ -58,8 +59,38 @@ class CobaltTUI:
|
|
|
58
59
|
title = " ".join(words[:4]).strip()
|
|
59
60
|
return title.title() if title else "New chat"
|
|
60
61
|
|
|
61
|
-
def _should_handle_shortcut(self, ch: int) -> bool:
|
|
62
|
-
return ch in (14, 4)
|
|
62
|
+
def _should_handle_shortcut(self, ch: int, input_focused: bool = False) -> bool:
|
|
63
|
+
return not input_focused and ch in (14, 4, ord("n"), ord("d"))
|
|
64
|
+
|
|
65
|
+
def _input_box_geometry(self, main_x: int, main_y: int, main_w: int, main_h: int, input_text: str) -> tuple[int, int, int, int, list[str]]:
|
|
66
|
+
box_width = max(20, main_w - 4)
|
|
67
|
+
text_width = max(10, box_width - 6)
|
|
68
|
+
input_lines = textwrap.wrap(
|
|
69
|
+
input_text,
|
|
70
|
+
width=text_width,
|
|
71
|
+
replace_whitespace=False,
|
|
72
|
+
drop_whitespace=False,
|
|
73
|
+
break_long_words=True,
|
|
74
|
+
break_on_hyphens=False,
|
|
75
|
+
) or [""]
|
|
76
|
+
max_lines = max(1, min(6, main_h - 6))
|
|
77
|
+
input_lines = input_lines[-max_lines:]
|
|
78
|
+
box_height = len(input_lines) + 3
|
|
79
|
+
box_x = main_x + 2
|
|
80
|
+
box_y = main_y + main_h - box_height - 1
|
|
81
|
+
return box_x, box_y, box_width, box_height, input_lines
|
|
82
|
+
|
|
83
|
+
def _draw_input_box(self, stdscr, box_x: int, box_y: int, box_width: int, box_height: int, input_lines: list[str], focused: bool) -> None:
|
|
84
|
+
rule = "-" * max(0, box_width - 2)
|
|
85
|
+
top_label = " Ask AI "
|
|
86
|
+
top = f"-{top_label}{rule}"
|
|
87
|
+
bottom_label = " [Enter to submit] "
|
|
88
|
+
bottom = f"-{'-' * max(0, box_width - len(bottom_label) - 2)}{bottom_label}-"
|
|
89
|
+
stdscr.addstr(box_y, box_x, top[:box_width])
|
|
90
|
+
for index, line in enumerate(input_lines):
|
|
91
|
+
prompt = "> " if index == 0 else " "
|
|
92
|
+
stdscr.addstr(box_y + 1 + index, box_x, f"{prompt}{line}"[:box_width])
|
|
93
|
+
stdscr.addstr(box_y + box_height - 1, box_x, bottom[:box_width], curses.A_BOLD if focused else curses.A_DIM)
|
|
63
94
|
|
|
64
95
|
def _compose_lines(self, text: str, width: int) -> list[str]:
|
|
65
96
|
lines: list[str] = []
|
|
@@ -116,15 +147,15 @@ class CobaltTUI:
|
|
|
116
147
|
main_h = max(8, height - 4)
|
|
117
148
|
self._draw_box(stdscr, main_y, main_x, main_h, main_w, f" {self.history.title or 'conversation'} ")
|
|
118
149
|
|
|
150
|
+
box_x, box_y, box_width, box_height, input_lines = self._input_box_geometry(main_x, main_y, main_w, main_h, input_text)
|
|
119
151
|
if not self.history.messages:
|
|
152
|
+
content_height = max(1, box_y - main_y - 1)
|
|
153
|
+
art_start = main_y + max(1, (content_height - len(self.ascii_lines)) // 2)
|
|
120
154
|
for idx, line in enumerate(self.ascii_lines):
|
|
121
|
-
y =
|
|
155
|
+
y = art_start + idx
|
|
122
156
|
x = main_x + max(0, (main_w - len(line)) // 2)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
prompt_y = main_y + 2 + len(self.ascii_lines) + 2
|
|
126
|
-
prompt_x = main_x + max(0, (main_w - len(prompt)) // 2)
|
|
127
|
-
stdscr.addstr(prompt_y, prompt_x, prompt[: max(0, main_w - 2)], curses.A_REVERSE)
|
|
157
|
+
if y < box_y:
|
|
158
|
+
stdscr.addstr(y, x, line[: max(0, main_w - 2)])
|
|
128
159
|
else:
|
|
129
160
|
rows: list[str] = []
|
|
130
161
|
for message in self.history.latest(18):
|
|
@@ -135,16 +166,15 @@ class CobaltTUI:
|
|
|
135
166
|
rows.append(f"{prefix}{line}" if prefix and line else line)
|
|
136
167
|
prefix = ""
|
|
137
168
|
|
|
138
|
-
visible = rows[-max(
|
|
169
|
+
visible = rows[-max(1, box_y - main_y - 2):]
|
|
139
170
|
for idx, line in enumerate(visible[: main_h - 4]):
|
|
140
171
|
y = main_y + 1 + idx
|
|
172
|
+
if y >= box_y:
|
|
173
|
+
break
|
|
141
174
|
clipped = line[: max(0, main_w - 4)]
|
|
142
175
|
stdscr.addstr(y, main_x + 2, clipped)
|
|
143
176
|
|
|
144
|
-
|
|
145
|
-
prompt_y = main_y + main_h - 3
|
|
146
|
-
prompt_x = main_x + max(0, (main_w - len(prompt)) // 2)
|
|
147
|
-
stdscr.addstr(prompt_y, prompt_x, prompt[: max(0, main_w - 2)], curses.A_REVERSE)
|
|
177
|
+
self._draw_input_box(stdscr, box_x, box_y, box_width, box_height, input_lines, self.input_focused)
|
|
148
178
|
|
|
149
179
|
if status:
|
|
150
180
|
status_line = status[: max(0, width - 1)]
|
|
@@ -190,6 +220,16 @@ class CobaltTUI:
|
|
|
190
220
|
return True
|
|
191
221
|
return False
|
|
192
222
|
|
|
223
|
+
def _click_is_in_input(self, x: int, y: int, input_text: str) -> bool:
|
|
224
|
+
height, width = curses.LINES, curses.COLS
|
|
225
|
+
sidebar_w = min(26, max(20, width // 4))
|
|
226
|
+
main_x = 3 + sidebar_w
|
|
227
|
+
main_y = 1
|
|
228
|
+
main_w = max(20, width - main_x - 2)
|
|
229
|
+
main_h = max(8, height - 4)
|
|
230
|
+
box_x, box_y, box_width, box_height, _ = self._input_box_geometry(main_x, main_y, main_w, main_h, input_text)
|
|
231
|
+
return box_x <= x < box_x + box_width and box_y <= y < box_y + box_height
|
|
232
|
+
|
|
193
233
|
def run(self) -> int:
|
|
194
234
|
def _main(stdscr):
|
|
195
235
|
curses.curs_set(0)
|
|
@@ -202,6 +242,7 @@ class CobaltTUI:
|
|
|
202
242
|
curses.noecho()
|
|
203
243
|
curses.mousemask(curses.BUTTON1_PRESSED | curses.BUTTON1_RELEASED | curses.BUTTON1_CLICKED)
|
|
204
244
|
input_text = ""
|
|
245
|
+
self.input_focused = True
|
|
205
246
|
status = ""
|
|
206
247
|
|
|
207
248
|
while True:
|
|
@@ -210,15 +251,22 @@ class CobaltTUI:
|
|
|
210
251
|
|
|
211
252
|
if ch == curses.KEY_MOUSE:
|
|
212
253
|
_, x, y, _, button_state = curses.getmouse()
|
|
254
|
+
if button_state & (curses.BUTTON1_PRESSED | curses.BUTTON1_CLICKED | curses.BUTTON1_RELEASED) and self._click_is_in_input(x, y, input_text):
|
|
255
|
+
self.input_focused = True
|
|
256
|
+
continue
|
|
213
257
|
if button_state & (curses.BUTTON1_PRESSED | curses.BUTTON1_CLICKED | curses.BUTTON1_RELEASED) and self._handle_mouse_click(x, y):
|
|
214
258
|
status = "Chat selected"
|
|
215
259
|
input_text = ""
|
|
260
|
+
self.input_focused = False
|
|
216
261
|
continue
|
|
217
262
|
|
|
218
263
|
if ch == 3:
|
|
219
264
|
break
|
|
220
|
-
if
|
|
221
|
-
|
|
265
|
+
if ch == 27:
|
|
266
|
+
self.input_focused = False
|
|
267
|
+
continue
|
|
268
|
+
if self._should_handle_shortcut(ch, self.input_focused):
|
|
269
|
+
if ch in (14, ord("n")):
|
|
222
270
|
new_chat = self.chat_store.new_chat(title="New chat")
|
|
223
271
|
self.current_chat = new_chat
|
|
224
272
|
self.history = self.current_chat
|
|
@@ -228,7 +276,7 @@ class CobaltTUI:
|
|
|
228
276
|
status = "New chat started"
|
|
229
277
|
continue
|
|
230
278
|
|
|
231
|
-
if ch
|
|
279
|
+
if ch in (4, ord("d")) and self.selected_chat_path:
|
|
232
280
|
target = Path(self.selected_chat_path)
|
|
233
281
|
if target.exists():
|
|
234
282
|
self.chat_store.delete_chat(target)
|
|
@@ -263,9 +311,8 @@ class CobaltTUI:
|
|
|
263
311
|
if ch in (curses.KEY_RESIZE,):
|
|
264
312
|
continue
|
|
265
313
|
if 32 <= ch <= 126:
|
|
314
|
+
self.input_focused = True
|
|
266
315
|
input_text += chr(ch)
|
|
267
|
-
elif ch == 27:
|
|
268
|
-
break
|
|
269
316
|
|
|
270
317
|
return 0
|
|
271
318
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cobalt-cli-linux
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Agentic Groq-powered CLI assistant for Debian Linux
|
|
5
5
|
Author: Cobalt
|
|
6
6
|
License-Expression: MIT
|
|
@@ -77,6 +77,8 @@ cobalt --interactive
|
|
|
77
77
|
|
|
78
78
|
This launches a fullscreen TUI that feels similar to a professional agent console, with the message stream and a compact prompt area instead of a raw shell prompt.
|
|
79
79
|
|
|
80
|
+
In the interface, press `Escape` to unfocus the prompt. While unfocused, `n` starts a new chat and `d` deletes the selected chat; click the prompt or type a character to focus it again. Long prompts wrap inside the prompt box.
|
|
81
|
+
|
|
80
82
|
You can also pass arguments directly:
|
|
81
83
|
|
|
82
84
|
```bash
|
|
@@ -51,9 +51,11 @@ def test_tui_shortcuts_ignore_typed_letters() -> None:
|
|
|
51
51
|
|
|
52
52
|
assert tui._should_handle_shortcut(14) is True
|
|
53
53
|
assert tui._should_handle_shortcut(4) is True
|
|
54
|
+
assert tui._should_handle_shortcut(ord("n")) is True
|
|
55
|
+
assert tui._should_handle_shortcut(ord("d")) is True
|
|
54
56
|
assert tui._should_handle_shortcut(ord("q")) is False
|
|
55
|
-
assert tui._should_handle_shortcut(ord("n")) is False
|
|
56
|
-
assert tui._should_handle_shortcut(ord("d")) is False
|
|
57
|
+
assert tui._should_handle_shortcut(ord("n"), True) is False
|
|
58
|
+
assert tui._should_handle_shortcut(ord("d"), True) is False
|
|
57
59
|
|
|
58
60
|
|
|
59
61
|
def test_q_is_not_a_shortcut_when_typing() -> None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.2}/src/cobalt_cli_linux.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.2}/src/cobalt_cli_linux.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.2}/src/cobalt_cli_linux.egg-info/requires.txt
RENAMED
|
File without changes
|
{cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.2}/src/cobalt_cli_linux.egg-info/top_level.txt
RENAMED
|
File without changes
|