opencode-db 0.6.1__tar.gz → 0.6.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.
Files changed (36) hide show
  1. {opencode_db-0.6.1 → opencode_db-0.6.2}/PKG-INFO +1 -1
  2. {opencode_db-0.6.1 → opencode_db-0.6.2}/formatters.py +16 -6
  3. {opencode_db-0.6.1 → opencode_db-0.6.2}/opencode_db.egg-info/PKG-INFO +1 -1
  4. {opencode_db-0.6.1 → opencode_db-0.6.2}/pyproject.toml +1 -1
  5. {opencode_db-0.6.1 → opencode_db-0.6.2}/tests/test_db.py +72 -0
  6. {opencode_db-0.6.1 → opencode_db-0.6.2}/tests/test_formatters.py +45 -0
  7. {opencode_db-0.6.1 → opencode_db-0.6.2}/LICENSE +0 -0
  8. {opencode_db-0.6.1 → opencode_db-0.6.2}/README.md +0 -0
  9. {opencode_db-0.6.1 → opencode_db-0.6.2}/README_PYPI.md +0 -0
  10. {opencode_db-0.6.1 → opencode_db-0.6.2}/cli.py +0 -0
  11. {opencode_db-0.6.1 → opencode_db-0.6.2}/cmd_costs.py +0 -0
  12. {opencode_db-0.6.1 → opencode_db-0.6.2}/cmd_delete.py +0 -0
  13. {opencode_db-0.6.1 → opencode_db-0.6.2}/cmd_export.py +0 -0
  14. {opencode_db-0.6.1 → opencode_db-0.6.2}/cmd_help.py +0 -0
  15. {opencode_db-0.6.1 → opencode_db-0.6.2}/cmd_info.py +0 -0
  16. {opencode_db-0.6.1 → opencode_db-0.6.2}/cmd_list.py +0 -0
  17. {opencode_db-0.6.1 → opencode_db-0.6.2}/cmd_projects.py +0 -0
  18. {opencode_db-0.6.1 → opencode_db-0.6.2}/cmd_prune.py +0 -0
  19. {opencode_db-0.6.1 → opencode_db-0.6.2}/cmd_search.py +0 -0
  20. {opencode_db-0.6.1 → opencode_db-0.6.2}/cmd_stats.py +0 -0
  21. {opencode_db-0.6.1 → opencode_db-0.6.2}/cmd_todos.py +0 -0
  22. {opencode_db-0.6.1 → opencode_db-0.6.2}/cmd_tree.py +0 -0
  23. {opencode_db-0.6.1 → opencode_db-0.6.2}/cmd_vacuum.py +0 -0
  24. {opencode_db-0.6.1 → opencode_db-0.6.2}/commands/__init__.py +0 -0
  25. {opencode_db-0.6.1 → opencode_db-0.6.2}/config.py +0 -0
  26. {opencode_db-0.6.1 → opencode_db-0.6.2}/db.py +0 -0
  27. {opencode_db-0.6.1 → opencode_db-0.6.2}/i18n.py +0 -0
  28. {opencode_db-0.6.1 → opencode_db-0.6.2}/opencode_db.egg-info/SOURCES.txt +0 -0
  29. {opencode_db-0.6.1 → opencode_db-0.6.2}/opencode_db.egg-info/dependency_links.txt +0 -0
  30. {opencode_db-0.6.1 → opencode_db-0.6.2}/opencode_db.egg-info/entry_points.txt +0 -0
  31. {opencode_db-0.6.1 → opencode_db-0.6.2}/opencode_db.egg-info/top_level.txt +0 -0
  32. {opencode_db-0.6.1 → opencode_db-0.6.2}/setup.cfg +0 -0
  33. {opencode_db-0.6.1 → opencode_db-0.6.2}/tests/test_commands.py +0 -0
  34. {opencode_db-0.6.1 → opencode_db-0.6.2}/tests/test_i18n.py +0 -0
  35. {opencode_db-0.6.1 → opencode_db-0.6.2}/tests/test_utils.py +0 -0
  36. {opencode_db-0.6.1 → opencode_db-0.6.2}/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opencode-db
3
- Version: 0.6.1
3
+ Version: 0.6.2
4
4
  Summary: OpenCode database CLI manager — browse, export, analyze, clean up sessions
5
5
  Project-URL: Homepage, https://github.com/VasilevNStas/opencode-db
6
6
  Project-URL: Source, https://github.com/VasilevNStas/opencode-db
@@ -1,18 +1,21 @@
1
1
  """Форматирование вывода: таблицы, JSON, Markdown."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  import json
4
6
  import os
7
+ from typing import Any
5
8
 
6
9
  from config import IGNORE_DIRS
7
10
  from i18n import _
8
11
 
9
12
 
10
- def print_json(data) -> None:
13
+ def print_json(data: Any) -> None:
11
14
  """Выводит данные в JSON-формате."""
12
15
  print(json.dumps(data, ensure_ascii=False, indent=2, default=str))
13
16
 
14
17
 
15
- def print_table_simple(headers, rows, sep=" ") -> None:
18
+ def print_table_simple(headers: list[str], rows: list[list], sep: str = " ") -> None:
16
19
  """Выводит таблицу с авто-шириной колонок."""
17
20
  if not rows:
18
21
  print(_("empty"))
@@ -37,7 +40,7 @@ def print_table_simple(headers, rows, sep=" ") -> None:
37
40
  print(_("fmt.rows_count", n=len(rows)))
38
41
 
39
42
 
40
- def summarize_val(v, max_len=120) -> str:
43
+ def summarize_val(v: Any, max_len: int = 120) -> str:
41
44
  """Обрезает длинное значение."""
42
45
  s = str(v)
43
46
  if len(s) > max_len:
@@ -45,7 +48,7 @@ def summarize_val(v, max_len=120) -> str:
45
48
  return s
46
49
 
47
50
 
48
- def format_part_to_md(part, full=False):
51
+ def format_part_to_md(part: dict, full: bool = False) -> str:
49
52
  """Преобразует часть сообщения в строку Markdown.
50
53
 
51
54
  Args:
@@ -149,7 +152,7 @@ def format_part_to_md(part, full=False):
149
152
  return ""
150
153
 
151
154
 
152
- def collect_snapshot(project_dir):
155
+ def collect_snapshot(project_dir: str) -> dict[str, Any]:
153
156
  """Собирает статистику по проекту для log.md.
154
157
 
155
158
  Returns:
@@ -182,7 +185,14 @@ def collect_snapshot(project_dir):
182
185
  return snapshot
183
186
 
184
187
 
185
- def update_log(output_dir, title, note, dialog_filename, snapshot, now_str) -> None:
188
+ def update_log(
189
+ output_dir: str,
190
+ title: str,
191
+ note: str | None,
192
+ dialog_filename: str | None,
193
+ snapshot: dict[str, Any],
194
+ now_str: str,
195
+ ) -> None:
186
196
  """Создаёт или дополняет log.md в директории проекта."""
187
197
  log_path = os.path.join(output_dir, "log.md")
188
198
  heading = f"{title}: {note}" if note else title
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opencode-db
3
- Version: 0.6.1
3
+ Version: 0.6.2
4
4
  Summary: OpenCode database CLI manager — browse, export, analyze, clean up sessions
5
5
  Project-URL: Homepage, https://github.com/VasilevNStas/opencode-db
6
6
  Project-URL: Source, https://github.com/VasilevNStas/opencode-db
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "opencode-db"
7
- version = "0.6.1"
7
+ version = "0.6.2"
8
8
  description = "OpenCode database CLI manager — browse, export, analyze, clean up sessions"
9
9
  readme = "README_PYPI.md"
10
10
  requires-python = ">=3.12"
@@ -275,3 +275,75 @@ class TestGetMessages:
275
275
  )
276
276
  messages = get_messages(db, "ses_001")
277
277
  assert len(messages) == 2
278
+
279
+
280
+ class TestEdgeCases:
281
+ def test_session_null_title_and_model(self, db) -> None:
282
+ db.execute(
283
+ "INSERT INTO session (id, time_created, title, model, cost) VALUES (?, ?, ?, ?, ?)",
284
+ ("ses_null_meta", 50000, None, None, 0.0),
285
+ )
286
+ db.commit()
287
+ info = get_session_info(db, "ses_null_meta")
288
+ assert info["title"] is None
289
+ assert info["model"] is None
290
+
291
+ def test_session_zero_cost(self, db) -> None:
292
+ db.execute(
293
+ "INSERT INTO session (id, time_created, cost) VALUES (?, ?, ?)",
294
+ ("ses_zero_cost", 55000, 0.0),
295
+ )
296
+ db.commit()
297
+ info = get_session_info(db, "ses_zero_cost")
298
+ assert info["cost"] == 0.0
299
+
300
+ def test_session_null_parent(self, db) -> None:
301
+ db.execute(
302
+ "INSERT INTO session (id, time_created, parent_id) VALUES (?, ?, ?)",
303
+ ("ses_orphan", 60000, None),
304
+ )
305
+ db.commit()
306
+ children = get_children_sessions(db, "ses_orphan")
307
+ assert children == []
308
+
309
+ def test_part_without_type_has_empty_parts(self, db) -> None:
310
+ db.execute(
311
+ "INSERT INTO session (id, time_created) VALUES (?, ?)",
312
+ ("ses_skip", 1000),
313
+ )
314
+ db.execute(
315
+ "INSERT INTO message (id, session_id, time_created, data) VALUES (?, ?, ?, ?)",
316
+ ("msg_skip", "ses_skip", 1000, json.dumps({"role": "user"})),
317
+ )
318
+ db.execute(
319
+ "INSERT INTO part (id, message_id, session_id, time_created, data) VALUES (?, ?, ?, ?, ?)",
320
+ ("part_skip", "msg_skip", "ses_skip", 1000, json.dumps({"text": "no type"})),
321
+ )
322
+ db.commit()
323
+ messages = get_messages(db, "ses_skip")
324
+ assert len(messages) == 1
325
+ assert len(messages[0]["parts"]) == 0
326
+
327
+ def test_message_null_role(self, db) -> None:
328
+ db.execute(
329
+ "INSERT INTO session (id, time_created) VALUES (?, ?)",
330
+ ("ses_edge_b", 2000),
331
+ )
332
+ db.execute(
333
+ "INSERT INTO message (id, session_id, time_created, data) VALUES (?, ?, ?, ?)",
334
+ ("msg_edge_b", "ses_edge_b", 2000, json.dumps({"role": None})),
335
+ )
336
+ db.execute(
337
+ "INSERT INTO part (id, message_id, session_id, time_created, data) VALUES (?, ?, ?, ?, ?)",
338
+ (
339
+ "part_edge_b",
340
+ "msg_edge_b",
341
+ "ses_edge_b",
342
+ 2000,
343
+ json.dumps({"type": "text", "text": "hi"}),
344
+ ),
345
+ )
346
+ db.commit()
347
+ messages = get_messages(db, "ses_edge_b")
348
+ assert len(messages) == 1
349
+ assert messages[0]["role"] is None or messages[0]["role"] == "?"
@@ -171,3 +171,48 @@ class TestFormatPartToMd:
171
171
  full=True,
172
172
  )
173
173
  assert "line" in result
174
+
175
+ def test_unknown_type_returns_empty(self):
176
+ result = format_part_to_md({"type": "unknown_type"})
177
+ assert result == ""
178
+
179
+ def test_empty_part_returns_empty(self):
180
+ result = format_part_to_md({"type": "text", "text": ""})
181
+ assert result == ""
182
+
183
+ def test_tool_without_input_key(self):
184
+ result = format_part_to_md(
185
+ {
186
+ "type": "tool",
187
+ "tool": "bash",
188
+ }
189
+ )
190
+ assert "bash" in result
191
+
192
+ def test_tool_with_null_output(self):
193
+ result = format_part_to_md(
194
+ {
195
+ "type": "tool",
196
+ "tool": "bash",
197
+ "input": {"command": "ls"},
198
+ "output": None,
199
+ "status": "completed",
200
+ }
201
+ )
202
+ assert "bash" in result
203
+
204
+ def test_tool_with_unknown_status(self):
205
+ result = format_part_to_md(
206
+ {
207
+ "type": "tool",
208
+ "tool": "bash",
209
+ "input": {"command": "ls"},
210
+ "output": "",
211
+ "status": "unknown",
212
+ }
213
+ )
214
+ assert "🔧" in result
215
+
216
+ def test_reasoning_empty_text(self):
217
+ result = format_part_to_md({"type": "reasoning", "text": ""})
218
+ assert result == ""
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
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