cobalt-cli-linux 0.1.4__tar.gz → 0.1.5__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.4 → cobalt_cli_linux-0.1.5}/PKG-INFO +3 -1
- {cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/README.md +2 -0
- {cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/pyproject.toml +1 -1
- cobalt_cli_linux-0.1.5/src/cobalt_cli_linux/ASCII.txt +5 -0
- {cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/src/cobalt_cli_linux/profiles.py +25 -2
- {cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/src/cobalt_cli_linux/tui.py +68 -1
- {cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/src/cobalt_cli_linux.egg-info/PKG-INFO +3 -1
- {cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/tests/test_cli.py +11 -0
- cobalt_cli_linux-0.1.4/src/cobalt_cli_linux/ASCII.txt +0 -5
- {cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/setup.cfg +0 -0
- {cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/src/cobalt_cli_linux/__init__.py +0 -0
- {cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/src/cobalt_cli_linux/__main__.py +0 -0
- {cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/src/cobalt_cli_linux/agent.py +0 -0
- {cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/src/cobalt_cli_linux/cli.py +0 -0
- {cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/src/cobalt_cli_linux/config.py +0 -0
- {cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/src/cobalt_cli_linux/deepseek_client.py +0 -0
- {cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/src/cobalt_cli_linux/executor.py +0 -0
- {cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/src/cobalt_cli_linux/groq_client.py +0 -0
- {cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/src/cobalt_cli_linux/history.py +0 -0
- {cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/src/cobalt_cli_linux.egg-info/SOURCES.txt +0 -0
- {cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/src/cobalt_cli_linux.egg-info/dependency_links.txt +0 -0
- {cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/src/cobalt_cli_linux.egg-info/entry_points.txt +0 -0
- {cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/src/cobalt_cli_linux.egg-info/requires.txt +0 -0
- {cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/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.5
|
|
4
4
|
Summary: Agentic Groq-powered CLI assistant for Debian Linux
|
|
5
5
|
Author: Cobalt
|
|
6
6
|
License-Expression: MIT
|
|
@@ -81,6 +81,8 @@ In the interface, click the `New chat` and `Delete chat` buttons to manage conve
|
|
|
81
81
|
|
|
82
82
|
Assistant reasoning is stored separately and can be expanded by clicking its `thinking` row. Markdown headings, lists, inline backticks, and fenced code are styled in the terminal. Guest profiles are limited to three prompts per hour.
|
|
83
83
|
|
|
84
|
+
The interface includes `Sign in` and `Sign up` buttons for local profiles. Profiles are stored locally; guest mode remains available with the prompt limit.
|
|
85
|
+
|
|
84
86
|
You can also pass arguments directly:
|
|
85
87
|
|
|
86
88
|
```bash
|
|
@@ -56,6 +56,8 @@ In the interface, click the `New chat` and `Delete chat` buttons to manage conve
|
|
|
56
56
|
|
|
57
57
|
Assistant reasoning is stored separately and can be expanded by clicking its `thinking` row. Markdown headings, lists, inline backticks, and fenced code are styled in the terminal. Guest profiles are limited to three prompts per hour.
|
|
58
58
|
|
|
59
|
+
The interface includes `Sign in` and `Sign up` buttons for local profiles. Profiles are stored locally; guest mode remains available with the prompt limit.
|
|
60
|
+
|
|
59
61
|
You can also pass arguments directly:
|
|
60
62
|
|
|
61
63
|
```bash
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import hashlib
|
|
3
4
|
import json
|
|
4
5
|
from datetime import datetime, timedelta, timezone
|
|
5
6
|
from pathlib import Path
|
|
@@ -13,12 +14,13 @@ class ProfileStore:
|
|
|
13
14
|
|
|
14
15
|
def _load(self) -> dict:
|
|
15
16
|
if not self.path.exists():
|
|
16
|
-
return {"profiles": {"guest": {"prompts": []}}, "active": "guest"}
|
|
17
|
+
return {"profiles": {"guest": {"prompts": []}}, "accounts": {}, "active": "guest"}
|
|
17
18
|
try:
|
|
18
19
|
value = json.loads(self.path.read_text(encoding="utf-8"))
|
|
19
20
|
except json.JSONDecodeError:
|
|
20
|
-
return {"profiles": {"guest": {"prompts": []}}, "active": "guest"}
|
|
21
|
+
return {"profiles": {"guest": {"prompts": []}}, "accounts": {}, "active": "guest"}
|
|
21
22
|
value.setdefault("profiles", {}).setdefault("guest", {"prompts": []})
|
|
23
|
+
value.setdefault("accounts", {})
|
|
22
24
|
value.setdefault("active", "guest")
|
|
23
25
|
return value
|
|
24
26
|
|
|
@@ -38,6 +40,27 @@ class ProfileStore:
|
|
|
38
40
|
self.save()
|
|
39
41
|
return name
|
|
40
42
|
|
|
43
|
+
def sign_up(self, name: str, password: str) -> str:
|
|
44
|
+
name = " ".join(name.strip().split())
|
|
45
|
+
if not name or not password:
|
|
46
|
+
raise ValueError("Name and password are required")
|
|
47
|
+
if name in self.data["accounts"]:
|
|
48
|
+
raise ValueError("Profile already exists")
|
|
49
|
+
digest = hashlib.sha256(password.encode("utf-8")).hexdigest()
|
|
50
|
+
self.data["accounts"][name] = digest
|
|
51
|
+
self.data["profiles"].setdefault(name, {"prompts": []})
|
|
52
|
+
self.data["active"] = name
|
|
53
|
+
self.save()
|
|
54
|
+
return name
|
|
55
|
+
|
|
56
|
+
def sign_in(self, name: str, password: str) -> str:
|
|
57
|
+
digest = hashlib.sha256(password.encode("utf-8")).hexdigest()
|
|
58
|
+
if self.data["accounts"].get(name) != digest:
|
|
59
|
+
raise ValueError("Invalid profile name or password")
|
|
60
|
+
self.data["active"] = name
|
|
61
|
+
self.save()
|
|
62
|
+
return name
|
|
63
|
+
|
|
41
64
|
def delete(self, name: str | None = None) -> bool:
|
|
42
65
|
name = name or self.active
|
|
43
66
|
if name == "guest" or name not in self.data["profiles"]:
|
|
@@ -27,6 +27,9 @@ class CobaltTUI:
|
|
|
27
27
|
self.ascii_lines = self._load_ascii_art()
|
|
28
28
|
self.thinking_expanded: set[int] = set()
|
|
29
29
|
self.profile_store = ProfileStore()
|
|
30
|
+
self.auth_view: str | None = None
|
|
31
|
+
self.auth_field = 0
|
|
32
|
+
self.auth_values = ["", ""]
|
|
30
33
|
|
|
31
34
|
def _load_ascii_art(self) -> list[str]:
|
|
32
35
|
ascii_path = Path(__file__).resolve().parents[2] / "ASCII.txt"
|
|
@@ -65,9 +68,15 @@ class CobaltTUI:
|
|
|
65
68
|
new_label = "[ New chat ]"
|
|
66
69
|
delete_x = max(1, width - len(delete_label) - 2)
|
|
67
70
|
new_x = max(1, delete_x - len(new_label) - 2)
|
|
71
|
+
signup_label = "[ Sign up ]"
|
|
72
|
+
signin_label = "[ Sign in ]"
|
|
73
|
+
signin_x = max(1, new_x - len(signin_label) - 2)
|
|
74
|
+
signup_x = max(1, signin_x - len(signup_label) - 2)
|
|
68
75
|
return {
|
|
69
76
|
"new": (new_x, 0, len(new_label), 1),
|
|
70
77
|
"delete": (delete_x, 0, len(delete_label), 1),
|
|
78
|
+
"signin": (signin_x, 0, len(signin_label), 1),
|
|
79
|
+
"signup": (signup_x, 0, len(signup_label), 1),
|
|
71
80
|
}
|
|
72
81
|
|
|
73
82
|
def _handle_button_click(self, x: int, y: int, width: int | None = None) -> str | None:
|
|
@@ -98,6 +107,38 @@ class CobaltTUI:
|
|
|
98
107
|
else:
|
|
99
108
|
self._start_new_chat()
|
|
100
109
|
|
|
110
|
+
def _open_auth(self, view: str) -> None:
|
|
111
|
+
self.auth_view = view
|
|
112
|
+
self.auth_field = 0
|
|
113
|
+
self.auth_values = ["", ""]
|
|
114
|
+
self.input_focused = True
|
|
115
|
+
|
|
116
|
+
def _submit_auth(self) -> None:
|
|
117
|
+
try:
|
|
118
|
+
if self.auth_view == "signup":
|
|
119
|
+
self.profile_store.sign_up(*self.auth_values)
|
|
120
|
+
elif self.auth_view == "signin":
|
|
121
|
+
self.profile_store.sign_in(*self.auth_values)
|
|
122
|
+
self.auth_view = None
|
|
123
|
+
self.auth_values = ["", ""]
|
|
124
|
+
except (ValueError, OSError) as exc:
|
|
125
|
+
self.auth_values[1] = str(exc)
|
|
126
|
+
|
|
127
|
+
def _render_auth(self, stdscr, height: int, width: int) -> None:
|
|
128
|
+
title = "Create profile" if self.auth_view == "signup" else "Sign in"
|
|
129
|
+
fields = ("Profile name", "Password")
|
|
130
|
+
panel_width = min(64, max(36, width - 8))
|
|
131
|
+
panel_x = max(2, (width - panel_width) // 2)
|
|
132
|
+
panel_y = max(3, (height - 12) // 2)
|
|
133
|
+
self._draw_box(stdscr, panel_y, panel_x, 10, panel_width, f" {title} ")
|
|
134
|
+
for index, label in enumerate(fields):
|
|
135
|
+
value = self.auth_values[index]
|
|
136
|
+
if index == 1 and value and not value.startswith("Invalid"):
|
|
137
|
+
value = "*" * len(value)
|
|
138
|
+
marker = "> " if index == self.auth_field else " "
|
|
139
|
+
stdscr.addstr(panel_y + 2 + index * 2, panel_x + 3, f"{marker}{label}: {value}"[: panel_width - 6])
|
|
140
|
+
stdscr.addstr(panel_y + 7, panel_x + 3, "Enter: next/submit | Escape: back"[: panel_width - 6], curses.A_DIM)
|
|
141
|
+
|
|
101
142
|
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]]:
|
|
102
143
|
box_width = max(20, main_w - 4)
|
|
103
144
|
text_width = max(10, box_width - 6)
|
|
@@ -188,9 +229,15 @@ class CobaltTUI:
|
|
|
188
229
|
header = " Cobalt Code | terminal agent "
|
|
189
230
|
stdscr.addstr(0, 1, header[: max(0, width - 2)], curses.A_BOLD)
|
|
190
231
|
for name, (button_x, button_y, button_width, _) in self._button_regions(width).items():
|
|
191
|
-
|
|
232
|
+
labels = {"new": "[ New chat ]", "delete": "[ Delete chat ]", "signin": "[ Sign in ]", "signup": "[ Sign up ]"}
|
|
233
|
+
label = labels[name]
|
|
192
234
|
stdscr.addstr(button_y, button_x, label[:button_width], curses.A_REVERSE if name == "new" else curses.A_NORMAL)
|
|
193
235
|
|
|
236
|
+
if self.auth_view:
|
|
237
|
+
self._render_auth(stdscr, height, width)
|
|
238
|
+
stdscr.refresh()
|
|
239
|
+
return
|
|
240
|
+
|
|
194
241
|
sidebar_w = min(26, max(20, width // 4))
|
|
195
242
|
sidebar_x = 1
|
|
196
243
|
sidebar_y = 6
|
|
@@ -349,6 +396,9 @@ class CobaltTUI:
|
|
|
349
396
|
if ch == curses.KEY_MOUSE:
|
|
350
397
|
_, x, y, _, button_state = curses.getmouse()
|
|
351
398
|
button = self._handle_button_click(x, y, stdscr.getmaxyx()[1]) if button_state & (curses.BUTTON1_PRESSED | curses.BUTTON1_CLICKED | curses.BUTTON1_RELEASED) else None
|
|
399
|
+
if button in ("signin", "signup"):
|
|
400
|
+
self._open_auth(button)
|
|
401
|
+
continue
|
|
352
402
|
if button == "new":
|
|
353
403
|
self._start_new_chat()
|
|
354
404
|
input_text = ""
|
|
@@ -373,8 +423,25 @@ class CobaltTUI:
|
|
|
373
423
|
if ch == 3:
|
|
374
424
|
break
|
|
375
425
|
if ch == 27:
|
|
426
|
+
if self.auth_view:
|
|
427
|
+
self.auth_view = None
|
|
428
|
+
self.auth_values = ["", ""]
|
|
429
|
+
continue
|
|
376
430
|
self.input_focused = False
|
|
377
431
|
continue
|
|
432
|
+
if self.auth_view:
|
|
433
|
+
if ch in (10, 13, curses.KEY_ENTER):
|
|
434
|
+
if self.auth_field == 0:
|
|
435
|
+
self.auth_field = 1
|
|
436
|
+
else:
|
|
437
|
+
self._submit_auth()
|
|
438
|
+
continue
|
|
439
|
+
if ch in (curses.KEY_BACKSPACE, 127, 8):
|
|
440
|
+
self.auth_values[self.auth_field] = self.auth_values[self.auth_field][:-1]
|
|
441
|
+
continue
|
|
442
|
+
if 32 <= ch <= 126:
|
|
443
|
+
self.auth_values[self.auth_field] += chr(ch)
|
|
444
|
+
continue
|
|
378
445
|
if ch in (10, 13, curses.KEY_ENTER):
|
|
379
446
|
if input_text.strip():
|
|
380
447
|
try:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cobalt-cli-linux
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.5
|
|
4
4
|
Summary: Agentic Groq-powered CLI assistant for Debian Linux
|
|
5
5
|
Author: Cobalt
|
|
6
6
|
License-Expression: MIT
|
|
@@ -81,6 +81,8 @@ In the interface, click the `New chat` and `Delete chat` buttons to manage conve
|
|
|
81
81
|
|
|
82
82
|
Assistant reasoning is stored separately and can be expanded by clicking its `thinking` row. Markdown headings, lists, inline backticks, and fenced code are styled in the terminal. Guest profiles are limited to three prompts per hour.
|
|
83
83
|
|
|
84
|
+
The interface includes `Sign in` and `Sign up` buttons for local profiles. Profiles are stored locally; guest mode remains available with the prompt limit.
|
|
85
|
+
|
|
84
86
|
You can also pass arguments directly:
|
|
85
87
|
|
|
86
88
|
```bash
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
3
5
|
from cobalt_cli_linux import cli
|
|
4
6
|
from cobalt_cli_linux.agent import CobaltAgent
|
|
5
7
|
from cobalt_cli_linux.config import Settings
|
|
@@ -133,6 +135,15 @@ def test_profiles_create_delete_and_guest_limit(tmp_path: Path) -> None:
|
|
|
133
135
|
assert store.active == "guest"
|
|
134
136
|
|
|
135
137
|
|
|
138
|
+
def test_profiles_sign_up_and_sign_in(tmp_path: Path) -> None:
|
|
139
|
+
store = ProfileStore(tmp_path / "profiles.json")
|
|
140
|
+
|
|
141
|
+
assert store.sign_up("Ada", "secret") == "Ada"
|
|
142
|
+
assert store.sign_in("Ada", "secret") == "Ada"
|
|
143
|
+
with pytest.raises(ValueError):
|
|
144
|
+
store.sign_in("Ada", "wrong")
|
|
145
|
+
|
|
146
|
+
|
|
136
147
|
def test_tui_empty_chat_does_not_load_prompt_art_as_body_content(tmp_path: Path) -> None:
|
|
137
148
|
tui = CobaltTUI(settings=Settings())
|
|
138
149
|
|
|
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.4 → cobalt_cli_linux-0.1.5}/src/cobalt_cli_linux.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/src/cobalt_cli_linux.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/src/cobalt_cli_linux.egg-info/requires.txt
RENAMED
|
File without changes
|
{cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/src/cobalt_cli_linux.egg-info/top_level.txt
RENAMED
|
File without changes
|