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.
Files changed (24) hide show
  1. {cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/PKG-INFO +3 -1
  2. {cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/README.md +2 -0
  3. {cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/pyproject.toml +1 -1
  4. cobalt_cli_linux-0.1.5/src/cobalt_cli_linux/ASCII.txt +5 -0
  5. {cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/src/cobalt_cli_linux/profiles.py +25 -2
  6. {cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/src/cobalt_cli_linux/tui.py +68 -1
  7. {cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/src/cobalt_cli_linux.egg-info/PKG-INFO +3 -1
  8. {cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/tests/test_cli.py +11 -0
  9. cobalt_cli_linux-0.1.4/src/cobalt_cli_linux/ASCII.txt +0 -5
  10. {cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/setup.cfg +0 -0
  11. {cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/src/cobalt_cli_linux/__init__.py +0 -0
  12. {cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/src/cobalt_cli_linux/__main__.py +0 -0
  13. {cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/src/cobalt_cli_linux/agent.py +0 -0
  14. {cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/src/cobalt_cli_linux/cli.py +0 -0
  15. {cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/src/cobalt_cli_linux/config.py +0 -0
  16. {cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/src/cobalt_cli_linux/deepseek_client.py +0 -0
  17. {cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/src/cobalt_cli_linux/executor.py +0 -0
  18. {cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/src/cobalt_cli_linux/groq_client.py +0 -0
  19. {cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/src/cobalt_cli_linux/history.py +0 -0
  20. {cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/src/cobalt_cli_linux.egg-info/SOURCES.txt +0 -0
  21. {cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/src/cobalt_cli_linux.egg-info/dependency_links.txt +0 -0
  22. {cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/src/cobalt_cli_linux.egg-info/entry_points.txt +0 -0
  23. {cobalt_cli_linux-0.1.4 → cobalt_cli_linux-0.1.5}/src/cobalt_cli_linux.egg-info/requires.txt +0 -0
  24. {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.4
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cobalt-cli-linux"
7
- version = "0.1.4"
7
+ version = "0.1.5"
8
8
  description = "Agentic Groq-powered CLI assistant for Debian Linux"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -0,0 +1,5 @@
1
+ ____ _ _ _ ____ _
2
+ / ___|___ | |__ __ _| | |_ / ___|___ __| | ___
3
+ | | / _ \| '_ \ / _` | | __| | | / _ \ / _` |/ _ \
4
+ | |__| (_) | |_) | (_| | | |_ | |__| (_) | (_| | __/
5
+ \____\___/|_.__/ \__,_|_|\__| \____\___/ \__,_|\___|
@@ -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
- label = "[ New chat ]" if name == "new" else "[ Delete chat ]"
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.4
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
 
@@ -1,5 +0,0 @@
1
- ____ ___ ____ _ _ _____ ____ _ ___
2
- / ___/ _ \| __ ) / \ | | |_ _| / ___| | |_ _|
3
- | | | | | | _ \ / _ \ | | | | | | | | | |
4
- | |__| |_| | |_) / ___ \| |___| | | |___| |___ | |
5
- \____\___/|____/_/ \_\_____|_| \____|_____|___|