opencode-db 0.7.2__tar.gz → 0.8.0__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 (58) hide show
  1. {opencode_db-0.7.2/opencode_db.egg-info → opencode_db-0.8.0}/PKG-INFO +6 -2
  2. {opencode_db-0.7.2 → opencode_db-0.8.0}/README.md +95 -51
  3. {opencode_db-0.7.2 → opencode_db-0.8.0/docs}/README_PYPI.md +5 -1
  4. opencode_db-0.8.0/opencode_db/__init__.py +5 -0
  5. opencode_db-0.8.0/opencode_db/__main__.py +6 -0
  6. {opencode_db-0.7.2 → opencode_db-0.8.0/opencode_db}/cli.py +5 -5
  7. {opencode_db-0.7.2 → opencode_db-0.8.0/opencode_db}/commands/__init__.py +20 -16
  8. {opencode_db-0.7.2 → opencode_db-0.8.0/opencode_db/commands}/cmd_costs.py +24 -55
  9. {opencode_db-0.7.2 → opencode_db-0.8.0/opencode_db/commands}/cmd_delete.py +8 -3
  10. {opencode_db-0.7.2 → opencode_db-0.8.0/opencode_db/commands}/cmd_export.py +27 -33
  11. {opencode_db-0.7.2 → opencode_db-0.8.0/opencode_db/commands}/cmd_help.py +10 -2
  12. {opencode_db-0.7.2 → opencode_db-0.8.0/opencode_db/commands}/cmd_info.py +16 -25
  13. {opencode_db-0.7.2 → opencode_db-0.8.0/opencode_db/commands}/cmd_list.py +15 -47
  14. {opencode_db-0.7.2 → opencode_db-0.8.0/opencode_db/commands}/cmd_projects.py +6 -16
  15. {opencode_db-0.7.2 → opencode_db-0.8.0/opencode_db/commands}/cmd_prune.py +3 -3
  16. opencode_db-0.8.0/opencode_db/commands/cmd_schema.py +56 -0
  17. opencode_db-0.8.0/opencode_db/commands/cmd_search.py +71 -0
  18. {opencode_db-0.7.2 → opencode_db-0.8.0/opencode_db/commands}/cmd_stats.py +15 -42
  19. {opencode_db-0.7.2 → opencode_db-0.8.0/opencode_db/commands}/cmd_todos.py +14 -30
  20. opencode_db-0.8.0/opencode_db/commands/cmd_tree.py +101 -0
  21. {opencode_db-0.7.2 → opencode_db-0.8.0/opencode_db/commands}/cmd_vacuum.py +13 -6
  22. {opencode_db-0.7.2 → opencode_db-0.8.0/opencode_db/commands}/cmd_view.py +34 -37
  23. opencode_db-0.8.0/opencode_db/config.py +40 -0
  24. {opencode_db-0.7.2 → opencode_db-0.8.0/opencode_db}/db.py +33 -46
  25. {opencode_db-0.7.2 → opencode_db-0.8.0/opencode_db}/formatters.py +2 -2
  26. {opencode_db-0.7.2 → opencode_db-0.8.0/opencode_db}/i18n.py +35 -1
  27. opencode_db-0.8.0/opencode_db/schema.py +243 -0
  28. opencode_db-0.8.0/opencode_db/schema_manifest.json +500 -0
  29. opencode_db-0.8.0/opencode_db/store/__init__.py +20 -0
  30. opencode_db-0.8.0/opencode_db/store/base.py +286 -0
  31. opencode_db-0.8.0/opencode_db/store/v1.py +253 -0
  32. opencode_db-0.8.0/opencode_db/store/v2.py +459 -0
  33. {opencode_db-0.7.2 → opencode_db-0.8.0/opencode_db}/utils.py +1 -1
  34. {opencode_db-0.7.2 → opencode_db-0.8.0/opencode_db.egg-info}/PKG-INFO +6 -2
  35. opencode_db-0.8.0/opencode_db.egg-info/SOURCES.txt +50 -0
  36. opencode_db-0.8.0/opencode_db.egg-info/entry_points.txt +2 -0
  37. opencode_db-0.8.0/opencode_db.egg-info/top_level.txt +1 -0
  38. {opencode_db-0.7.2 → opencode_db-0.8.0}/pyproject.toml +9 -14
  39. {opencode_db-0.7.2 → opencode_db-0.8.0}/tests/test_commands.py +81 -81
  40. {opencode_db-0.7.2 → opencode_db-0.8.0}/tests/test_db.py +1 -1
  41. {opencode_db-0.7.2 → opencode_db-0.8.0}/tests/test_formatters.py +1 -1
  42. {opencode_db-0.7.2 → opencode_db-0.8.0}/tests/test_i18n.py +2 -2
  43. {opencode_db-0.7.2 → opencode_db-0.8.0}/tests/test_runtime.py +1 -1
  44. opencode_db-0.8.0/tests/test_schema.py +133 -0
  45. opencode_db-0.8.0/tests/test_store_v2.py +282 -0
  46. {opencode_db-0.7.2 → opencode_db-0.8.0}/tests/test_utils.py +1 -1
  47. opencode_db-0.7.2/cmd_search.py +0 -124
  48. opencode_db-0.7.2/cmd_tree.py +0 -110
  49. opencode_db-0.7.2/config.py +0 -16
  50. opencode_db-0.7.2/opencode_db.egg-info/SOURCES.txt +0 -39
  51. opencode_db-0.7.2/opencode_db.egg-info/entry_points.txt +0 -2
  52. opencode_db-0.7.2/opencode_db.egg-info/top_level.txt +0 -23
  53. {opencode_db-0.7.2 → opencode_db-0.8.0}/LICENSE +0 -0
  54. {opencode_db-0.7.2 → opencode_db-0.8.0/opencode_db}/markdown_to_ansi.py +0 -0
  55. {opencode_db-0.7.2 → opencode_db-0.8.0/opencode_db}/runtime.py +0 -0
  56. {opencode_db-0.7.2 → opencode_db-0.8.0}/opencode_db.egg-info/dependency_links.txt +0 -0
  57. {opencode_db-0.7.2 → opencode_db-0.8.0}/opencode_db.egg-info/requires.txt +0 -0
  58. {opencode_db-0.7.2 → opencode_db-0.8.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opencode-db
3
- Version: 0.7.2
3
+ Version: 0.8.0
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
@@ -17,7 +17,7 @@ Dynamic: license-file
17
17
  # opencode-db
18
18
 
19
19
  [![Python](https://img.shields.io/badge/python-3.12%2B-blue)](https://www.python.org/downloads/)
20
- [![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)
20
+ [![License](https://img.shields.io/badge/license-MIT-green)](https://github.com/VasilevNStas/opencode-db/blob/master/LICENSE)
21
21
 
22
22
  **CLI for OpenCode's local database.** OpenCode saves every agent session, conversation, token usage, and cost in a local SQLite file. This tool lets you inspect, export, analyze, and clean up that data.
23
23
 
@@ -34,6 +34,7 @@ uv tool install opencode-db
34
34
  opencode-db list # recent sessions
35
35
  opencode-db view # interactive session viewer (colored, scrollable)
36
36
  opencode-db stats # database summary
37
+ opencode-db schema # detect v1/v2 schema
37
38
  opencode-db costs --total # total token costs
38
39
  opencode-db export # export dialog to .md (interactive)
39
40
  opencode-db help # full reference
@@ -46,6 +47,7 @@ opencode-db help # full reference
46
47
  - **`export`** — экспорт диалога в Markdown (с поддержкой Obsidian)
47
48
  - **`delete`** / **`prune`** — удаление и массовая очистка с фильтрами
48
49
  - **`costs`** / **`stats`** — аналитика токенов и расходов
50
+ - **`schema`** — определяет v1/v2-схему OpenCode, миграции и доступные операции
49
51
  - **`--db-path`** / **`OPENCODE_DB`** — кастомный путь к БД
50
52
 
51
53
  ## Optional extras
@@ -60,3 +62,5 @@ pip install opencode-db # zero external dependencies
60
62
  - Python 3.12+ — the CLI checks the interpreter at startup and exits with a clear message instead of an `ImportError` on older versions
61
63
  - [OpenCode](https://opencode.ai) (must have been run at least once)
62
64
  - macOS / Linux
65
+
66
+ **Schema support:** OpenCode 1.19.x (v1) — read and write; OpenCode 2.x preview (v2) — read-only. Run `opencode-db schema` to check the detected schema. On v2, `delete`, `prune`, and `vacuum` are refused until write support is implemented.
@@ -8,10 +8,15 @@
8
8
 
9
9
  **OpenCode stores everything in a local SQLite database.** Every agent session, conversation, token usage, and cost is saved automatically. `opencode-db` is the CLI tool to work with that database — list sessions, export conversations to Markdown, track token costs, prune old data, and more.
10
10
 
11
+ [Русская версия](docs/README.ru.md)
12
+
13
+ > **Published on PyPI:** [opencode-db](https://pypi.org/project/opencode-db/) — install the ready-made package with `uv tool install opencode-db` or `pip install opencode-db`.
14
+
11
15
  ```
12
16
  opencode-db help # full reference with examples
13
17
  opencode-db list # list recent sessions
14
18
  opencode-db stats # database summary
19
+ opencode-db schema # detect v1/v2 schema
15
20
  opencode-db export # save a dialog to .md (interactive)
16
21
  opencode-db costs --total # total token costs
17
22
  ```
@@ -81,7 +86,7 @@ pipx install opencode-db
81
86
  pip install opencode-db
82
87
  ```
83
88
 
84
- [Zero external dependencies](https://treyhunner.com/2015/12/python-imports-and-path-manipulation/) — pure Python standard library only (`rich` is optional).
89
+ **Zero external dependencies** — runs on the Python standard library alone. Only Python 3.12+ is required; `rich` is optional for enhanced Markdown rendering.
85
90
 
86
91
  ### Manual install
87
92
 
@@ -110,8 +115,8 @@ if sys.version_info < (3, 12):
110
115
  PKG_DIR = os.path.expanduser("~/.local/share/opencode-db")
111
116
  if PKG_DIR not in sys.path:
112
117
  sys.path.insert(0, PKG_DIR)
113
- import cli
114
- sys.exit(cli.main())
118
+ from opencode_db.cli import main
119
+ sys.exit(main())
115
120
  SCRIPT
116
121
  chmod +x ~/.local/bin/opencode-db
117
122
  ```
@@ -410,6 +415,26 @@ opencode-db stats --json # JSON output
410
415
 
411
416
  ---
412
417
 
418
+ ### `schema` — detect database schema
419
+
420
+ Shows which OpenCode schema family the database uses, the applied migration, and which operations are supported.
421
+
422
+ ```bash
423
+ opencode-db schema # human-readable summary
424
+ opencode-db schema --json # JSON output
425
+ ```
426
+
427
+ Supported families:
428
+
429
+ | OpenCode | Schema | Support |
430
+ |---|---|---|
431
+ | 1.19.x | v1 | read + write |
432
+ | 2.x preview | v2 | read-only |
433
+
434
+ On a v2 database, `delete`, `prune`, and `vacuum` are refused until write support is implemented. Detection is based on tables and the migration journal, not on the OpenCode version string.
435
+
436
+ ---
437
+
413
438
  ### `todos` — view tasks from sessions
414
439
 
415
440
  Displays todo items created during OpenCode sessions.
@@ -507,56 +532,62 @@ Example output:
507
532
  ## Project architecture
508
533
 
509
534
  ```
510
- ~/.local/bin/opencode-db ← entry point (in $PATH)
511
- │
512
- ~/.local/share/opencode-db/ ← all source code
513
- │
514
- ├── __init__.py ← executable entry point
515
- ├── __main__.py ← python3 . (directory entry point)
516
- ├── cli.py ← argparse + command dispatcher
535
+ opencode-db/
536
+ ├── opencode_db/ ← Python package (installed as opencode_db)
537
+ │ ├── __init__.py ← package init + Python version guard
538
+ │ ├── __main__.py ← python3 -m opencode_db
539
+ │ ├── cli.py ← argparse + command dispatcher
540
+ │ ├── config.py ← constants and paths
541
+ │ ├── db.py ← SQLite queries
542
+ │ ├── formatters.py ← tables, JSON, Markdown, snapshots
543
+ │ ├── i18n.py ← localization (EN/RU, 296 keys)
544
+ │ ├── markdown_to_ansi.py ← Markdown → ANSI renderer for `view`
545
+ │ ├── runtime.py ← Python 3.12+ version guard
546
+ │ ├── utils.py ← date/token formatting, validation
547
+ │ └── commands/
548
+ │ ├── __init__.py ← command registry
549
+ │ ├── cmd_list.py ← opencode-db list
550
+ │ ├── cmd_info.py ← opencode-db info
551
+ │ ├── cmd_export.py ← opencode-db export
552
+ │ ├── cmd_delete.py ← opencode-db delete
553
+ │ ├── cmd_costs.py ← opencode-db costs
554
+ │ ├── cmd_prune.py ← opencode-db prune
555
+ │ ├── cmd_search.py ← opencode-db search
556
+ │ ├── cmd_tree.py ← opencode-db tree
557
+ │ ├── cmd_projects.py ← opencode-db projects
558
+ │ ├── cmd_stats.py ← opencode-db stats
559
+ │ ├── cmd_todos.py ← opencode-db todos
560
+ │ ├── cmd_vacuum.py ← opencode-db vacuum
561
+ │ ├── cmd_view.py ← opencode-db view
562
+ │ └── cmd_help.py ← opencode-db help
517
563
  │
518
- ├── config.py ← constants and paths
519
- ├── runtime.py ← Python 3.12+ version guard
520
- ├── db.py ← SQLite queries
521
- ├── utils.py ← date/token formatting, validation
522
- ├── formatters.py ← tables, JSON, Markdown, snapshots
523
- ├── i18n.py ← localization (EN/RU, 280 keys)
524
- ├── markdown_to_ansi.py ← Markdown → ANSI renderer for `view`
525
- │
526
- ├── commands/
527
- │ └── __init__.py ← command registry
528
- │
529
- ├── cmd_list.py ← opencode-db list
530
- ├── cmd_info.py ← opencode-db info
531
- ├── cmd_export.py ← opencode-db export
532
- ├── cmd_delete.py ← opencode-db delete
533
- ├── cmd_costs.py ← opencode-db costs
534
- ├── cmd_prune.py ← opencode-db prune
535
- ├── cmd_search.py ← opencode-db search
536
- ├── cmd_tree.py ← opencode-db tree
537
- ├── cmd_projects.py ← opencode-db projects
538
- ├── cmd_stats.py ← opencode-db stats
539
- ├── cmd_todos.py ← opencode-db todos
540
- ├── cmd_vacuum.py ← opencode-db vacuum
541
- ├── cmd_view.py ← opencode-db view
542
- └── cmd_help.py ← opencode-db help
543
-
544
- README.md ← this file (English)
545
- README.ru.md ← documentation in Russian
546
- opencode-db-commands.json ← OpenCode TUI custom commands config
564
+ ├── docs/
565
+ │ ├── README.ru.md ← documentation in Russian
566
+ │ └── README_PYPI.md ← PyPI page
567
+ ├── contrib/
568
+ │ └── opencode-db-commands.json ← OpenCode TUI custom commands config
569
+ ├── tests/
570
+ ├── README.md ← this file (English)
571
+ ├── LICENSE
572
+ ├── Makefile
573
+ ├── pyproject.toml
574
+ └── install.sh
547
575
  ```
548
576
 
549
577
  ### How it works
550
578
 
551
- 1. The launcher script at `~/.local/bin/opencode-db` (a shebang Python script) adds `~/.local/share/opencode-db/` to `sys.path` and calls `cli.main()`.
579
+ 1. The console script `opencode-db` (or `python3 -m opencode_db`) imports `opencode_db.cli` and calls `main()`.
552
580
  2. `runtime.require_python()` checks the interpreter before importing `cli`/`db`; on Python < 3.12 it prints a clear message with remediation steps instead of failing with `ImportError`.
553
581
  3. `cli.py` builds an `argparse` parser with subparsers from every command module and dispatches the call.
554
- 4. Each command is a separate `cmd_*.py` module exporting two functions: `register(subparsers)` and `run(args, db)`.
555
- 5. `db.py` opens a connection to OpenCode's SQLite database (`~/.local/share/opencode/opencode.db`).
556
- 6. Destructive operations (`delete`, `prune`) always require confirmation (or `--force`).
582
+ 4. Each command is a separate `opencode_db/commands/cmd_*.py` module exporting two functions: `register(subparsers)` and `run(args, db)`.
583
+ 5. `db.py` opens a connection to OpenCode's SQLite database (`~/.local/share/opencode/opencode.db`, with `opencode-next.db` fallback) and detects the schema family.
584
+ 6. `store/` selects `V1Store` or `V2Store`; commands read through the same interface regardless of the underlying schema.
585
+ 7. Destructive operations (`delete`, `prune`) always require confirmation (or `--force`); on v2 they are refused until write support lands.
557
586
 
558
587
  ### Database schema (key tables)
559
588
 
589
+ Stable OpenCode 1.19.x (v1):
590
+
560
591
  ```
561
592
  session — sessions: id, title, model (JSON), agent, cost, tokens_*, parent_id, summary_*
562
593
  message — messages: id, session_id, time_created, data (JSON: role, agent)
@@ -565,6 +596,19 @@ todo — tasks: session_id, content, status, priority
565
596
  project — projects: id, worktree, name
566
597
  ```
567
598
 
599
+ Preview OpenCode 2.x (v2):
600
+
601
+ ```
602
+ session_v2 — renamed session table with the same usage fields
603
+ session_message — typed JSON messages: user, assistant, synthetic, system, shell, compaction, ...
604
+ project — projects: id, worktree, name, time_active, ...
605
+ ```
606
+
607
+ v2 has no `message`, `part`, or `todo` tables. Assistant content is nested in
608
+ `session_message.data.content`, and todos are derived from the latest completed
609
+ `todowrite` tool call. Use `opencode-db schema` to inspect the detected family
610
+ before running write commands.
611
+
568
612
  ---
569
613
 
570
614
  ## Using inside OpenCode TUI
@@ -586,7 +630,7 @@ This runs the command directly and shows the output — **no tokens are consumed
586
630
 
587
631
  ### Custom `/db` command (uses AI, consumes tokens)
588
632
 
589
- Add the commands from `opencode-db-commands.json` to your project's `opencode.json`:
633
+ Add the commands from `contrib/opencode-db-commands.json` to your project's `opencode.json`:
590
634
 
591
635
  ```json
592
636
  {
@@ -614,7 +658,7 @@ Then in the TUI:
614
658
 
615
659
  A ready-to-use config file is provided at:
616
660
  ```
617
- ~/.local/share/opencode-db/opencode-db-commands.json
661
+ ~/.local/share/opencode-db/contrib/opencode-db-commands.json
618
662
  ```
619
663
 
620
664
  ---
@@ -645,7 +689,7 @@ opencode-db --lang en help # English help text
645
689
  opencode-db --lang en stats # English statistics
646
690
  ```
647
691
 
648
- The `--lang` flag applies to any command. All 280 UI strings are translated. Data content (session titles, project names, etc.) remains in its original language.
692
+ The `--lang` flag applies to any command. All 296 UI strings are translated. Data content (session titles, project names, etc.) remains in its original language.
649
693
 
650
694
  The language can also be set via environment variable `OPENCODE_DB_LANG=en`.
651
695
 
@@ -728,16 +772,16 @@ cd opencode-db
728
772
  uv venv --python 3.13
729
773
  source .venv/bin/activate
730
774
  uv pip install -e ".[rich]"
731
- uv pip install pytest ruff
775
+ uv pip install pytest ruff==0.17.1
732
776
 
733
- make check # check-python + ruff + 191 tests
777
+ make check # check-python + ruff + 205 tests
734
778
  ```
735
779
 
736
780
  `make check` fails fast with a clear message if the active interpreter is older than 3.12.
737
781
 
738
782
  ### Adding a new command
739
783
 
740
- 1. Create `cmd_xxx.py`:
784
+ 1. Create `opencode_db/commands/cmd_xxx.py`:
741
785
 
742
786
  ```python
743
787
  def register(subparsers):
@@ -749,7 +793,7 @@ def run(args, db):
749
793
  return 0
750
794
  ```
751
795
 
752
- 2. Register it in `commands/__init__.py`:
796
+ 2. Register it in `opencode_db/commands/__init__.py`:
753
797
 
754
798
  ```python
755
799
  import cmd_xxx
@@ -767,7 +811,7 @@ python3 ~/.local/share/opencode-db list
767
811
  Or from the repository root (Python 3.12+):
768
812
 
769
813
  ```bash
770
- python3 . list
814
+ python3 -m opencode_db list
771
815
  uvx --from . opencode-db list
772
816
  ```
773
817
 
@@ -1,7 +1,7 @@
1
1
  # opencode-db
2
2
 
3
3
  [![Python](https://img.shields.io/badge/python-3.12%2B-blue)](https://www.python.org/downloads/)
4
- [![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)
4
+ [![License](https://img.shields.io/badge/license-MIT-green)](https://github.com/VasilevNStas/opencode-db/blob/master/LICENSE)
5
5
 
6
6
  **CLI for OpenCode's local database.** OpenCode saves every agent session, conversation, token usage, and cost in a local SQLite file. This tool lets you inspect, export, analyze, and clean up that data.
7
7
 
@@ -18,6 +18,7 @@ uv tool install opencode-db
18
18
  opencode-db list # recent sessions
19
19
  opencode-db view # interactive session viewer (colored, scrollable)
20
20
  opencode-db stats # database summary
21
+ opencode-db schema # detect v1/v2 schema
21
22
  opencode-db costs --total # total token costs
22
23
  opencode-db export # export dialog to .md (interactive)
23
24
  opencode-db help # full reference
@@ -30,6 +31,7 @@ opencode-db help # full reference
30
31
  - **`export`** — экспорт диалога в Markdown (с поддержкой Obsidian)
31
32
  - **`delete`** / **`prune`** — удаление и массовая очистка с фильтрами
32
33
  - **`costs`** / **`stats`** — аналитика токенов и расходов
34
+ - **`schema`** — определяет v1/v2-схему OpenCode, миграции и доступные операции
33
35
  - **`--db-path`** / **`OPENCODE_DB`** — кастомный путь к БД
34
36
 
35
37
  ## Optional extras
@@ -44,3 +46,5 @@ pip install opencode-db # zero external dependencies
44
46
  - Python 3.12+ — the CLI checks the interpreter at startup and exits with a clear message instead of an `ImportError` on older versions
45
47
  - [OpenCode](https://opencode.ai) (must have been run at least once)
46
48
  - macOS / Linux
49
+
50
+ **Schema support:** OpenCode 1.19.x (v1) — read and write; OpenCode 2.x preview (v2) — read-only. Run `opencode-db schema` to check the detected schema. On v2, `delete`, `prune`, and `vacuum` are refused until write support is implemented.
@@ -0,0 +1,5 @@
1
+ """opencode-db: CLI for OpenCode's local SQLite database."""
2
+
3
+ from .runtime import require_python
4
+
5
+ require_python()
@@ -0,0 +1,6 @@
1
+ """Entry point for `python3 -m opencode_db`."""
2
+
3
+ from .cli import main
4
+
5
+ if __name__ == "__main__":
6
+ raise SystemExit(main())
@@ -14,13 +14,13 @@ import os
14
14
  import sys
15
15
  from typing import Any, Literal
16
16
 
17
- from runtime import require_python
17
+ from .runtime import require_python
18
18
 
19
19
  require_python()
20
20
 
21
- import commands # noqa: E402
22
- from db import get_db # noqa: E402
23
- from i18n import _, set_lang # noqa: E402
21
+ from . import commands # noqa: E402
22
+ from .db import get_db # noqa: E402
23
+ from .i18n import _, set_lang # noqa: E402
24
24
 
25
25
 
26
26
  def build_parser() -> argparse.ArgumentParser:
@@ -74,7 +74,7 @@ def main(argv=None) -> Any | Literal[0] | Literal[1]:
74
74
  if args.db_path:
75
75
  os.environ["OPENCODE_DB"] = args.db_path
76
76
 
77
- db = get_db()
77
+ db = get_db(allow_unknown=args.command in {"schema", "help"})
78
78
 
79
79
  try:
80
80
  handler = commands.get_handler(args.command)
@@ -5,25 +5,28 @@
5
5
  - run(args, db) — выполняет команду
6
6
 
7
7
  Добавление новой команды:
8
- 1. Создать cmd_xxx.py
9
- 2. Добавить import cmd_xxx
8
+ 1. Создать opencode_db/commands/cmd_xxx.py
9
+ 2. Добавить `from . import cmd_xxx`
10
10
  3. Добавить "xxx": cmd_xxx в COMMANDS
11
11
  """
12
12
 
13
- import cmd_costs
14
- import cmd_delete
15
- import cmd_export
16
- import cmd_help
17
- import cmd_info
18
- import cmd_list
19
- import cmd_projects
20
- import cmd_prune
21
- import cmd_search
22
- import cmd_stats
23
- import cmd_todos
24
- import cmd_tree
25
- import cmd_vacuum
26
- import cmd_view
13
+ from . import (
14
+ cmd_costs,
15
+ cmd_delete,
16
+ cmd_export,
17
+ cmd_help,
18
+ cmd_info,
19
+ cmd_list,
20
+ cmd_projects,
21
+ cmd_prune,
22
+ cmd_schema,
23
+ cmd_search,
24
+ cmd_stats,
25
+ cmd_todos,
26
+ cmd_tree,
27
+ cmd_vacuum,
28
+ cmd_view,
29
+ )
27
30
 
28
31
  # Словарь {имя_команды: модуль}
29
32
  # Модуль должен иметь register(subparsers) и run(args, db)
@@ -38,6 +41,7 @@ COMMANDS = {
38
41
  "tree": cmd_tree,
39
42
  "projects": cmd_projects,
40
43
  "stats": cmd_stats,
44
+ "schema": cmd_schema,
41
45
  "todos": cmd_todos,
42
46
  "vacuum": cmd_vacuum,
43
47
  "view": cmd_view,
@@ -5,9 +5,10 @@
5
5
 
6
6
  import argparse
7
7
 
8
- from db import SessionError, get_session_info, get_session_title, parse_model
9
- from i18n import _
10
- from utils import build_help_epilog, format_cost, format_tokens
8
+ from ..db import SessionError
9
+ from ..i18n import _
10
+ from ..store import open_store
11
+ from ..utils import build_help_epilog, format_cost, format_tokens
11
12
 
12
13
  _COSTS_EXAMPLES = [
13
14
  ("", "help.costs.e0"),
@@ -33,23 +34,24 @@ def register(subparsers) -> None:
33
34
 
34
35
 
35
36
  def run(args, db) -> int:
37
+ store = open_store(db)
36
38
  if args.session_id:
37
- return _show_session(db, args.session_id, args.json)
39
+ return _show_session(store, args.session_id, args.json)
38
40
 
39
41
  if args.total:
40
- return _show_total(db, args.project, args.json)
42
+ return _show_total(store, args.project, args.json)
41
43
 
42
- return _show_list(db, args.project, args.limit, args.json)
44
+ return _show_list(store, args.project, args.limit, args.json)
43
45
 
44
46
 
45
- def _show_session(db, session_id, as_json) -> int:
47
+ def _show_session(store, session_id, as_json) -> int:
46
48
  try:
47
- info = get_session_info(db, session_id)
49
+ info = store.get_session_info(session_id)
48
50
  except SessionError as e:
49
51
  print(e.message)
50
52
  return 1
51
- title = get_session_title(info)
52
- model = parse_model(info)
53
+ title = store.get_session_title(info)
54
+ model = store.parse_model(info)
53
55
 
54
56
  ti = info["tokens_input"] or 0
55
57
  to = info["tokens_output"] or 0
@@ -57,7 +59,7 @@ def _show_session(db, session_id, as_json) -> int:
57
59
  cost = info["cost"] or 0
58
60
 
59
61
  if as_json:
60
- from formatters import print_json
62
+ from ..formatters import print_json
61
63
 
62
64
  print_json(
63
65
  {
@@ -90,23 +92,8 @@ def _show_session(db, session_id, as_json) -> int:
90
92
  return 0
91
93
 
92
94
 
93
- def _show_total(db, project_id, as_json) -> int:
94
- where = "1=1"
95
- params = []
96
-
97
- if project_id:
98
- where = "s.project_id = ?"
99
- params.append(project_id)
100
-
101
- row = db.execute(
102
- f"""
103
- SELECT COUNT(*) as cnt,
104
- SUM(tokens_input) as ti, SUM(tokens_output) as tok_out,
105
- SUM(tokens_reasoning) as tr, SUM(cost) as c
106
- FROM session WHERE {where}
107
- """,
108
- params,
109
- ).fetchone()
95
+ def _show_total(store, project_id, as_json) -> int:
96
+ row = store.get_cost_total(project_id)
110
97
 
111
98
  cnt = row["cnt"] or 0
112
99
  total_cost = row["c"] or 0
@@ -115,7 +102,7 @@ def _show_total(db, project_id, as_json) -> int:
115
102
  total_tr = row["tr"] or 0
116
103
 
117
104
  if as_json:
118
- from formatters import print_json
105
+ from ..formatters import print_json
119
106
 
120
107
  print_json(
121
108
  {
@@ -143,37 +130,19 @@ def _show_total(db, project_id, as_json) -> int:
143
130
  return 0
144
131
 
145
132
 
146
- def _show_list(db, project_id, limit, as_json) -> int:
147
- where = "1=1"
148
- params = []
149
-
150
- if project_id:
151
- where = "s.project_id = ?"
152
- params.append(project_id)
153
-
154
- rows = db.execute(
155
- f"""
156
- SELECT s.id, s.title, s.model,
157
- s.tokens_input, s.tokens_output, s.tokens_reasoning,
158
- s.cost
159
- FROM session s
160
- WHERE {where}
161
- ORDER BY (s.tokens_input + s.tokens_output) DESC
162
- LIMIT ?
163
- """,
164
- (*params, limit),
165
- ).fetchall()
133
+ def _show_list(store, project_id, limit, as_json) -> int:
134
+ rows = store.get_cost_list(project_id, limit)
166
135
 
167
136
  if as_json:
168
- from formatters import print_json
137
+ from ..formatters import print_json
169
138
 
170
139
  data = []
171
140
  for r in rows:
172
141
  data.append(
173
142
  {
174
143
  "id": r["id"],
175
- "title": get_session_title(r),
176
- "model": parse_model(r),
144
+ "title": store.get_session_title(r),
145
+ "model": store.parse_model(r),
177
146
  "tokens_total": (r["tokens_input"] or 0)
178
147
  + (r["tokens_output"] or 0)
179
148
  + (r["tokens_reasoning"] or 0),
@@ -183,7 +152,7 @@ def _show_list(db, project_id, limit, as_json) -> int:
183
152
  print_json(data)
184
153
  return 0
185
154
 
186
- from formatters import print_table_simple
155
+ from ..formatters import print_table_simple
187
156
 
188
157
  table = []
189
158
  for r in rows:
@@ -193,8 +162,8 @@ def _show_list(db, project_id, limit, as_json) -> int:
193
162
  table.append(
194
163
  [
195
164
  r["id"][:24],
196
- get_session_title(r)[:28],
197
- parse_model(r)[:20],
165
+ store.get_session_title(r)[:28],
166
+ store.parse_model(r)[:20],
198
167
  format_tokens(total_tokens),
199
168
  format_cost(r["cost"] or 0),
200
169
  ]
@@ -2,7 +2,7 @@
2
2
 
3
3
  import argparse
4
4
 
5
- from db import (
5
+ from ..db import (
6
6
  SessionError,
7
7
  get_message_count,
8
8
  get_part_count,
@@ -11,8 +11,9 @@ from db import (
11
11
  get_session_title,
12
12
  parse_model,
13
13
  )
14
- from i18n import _
15
- from utils import build_help_epilog, confirm, format_cost, format_ts, parse_date, parse_time_spec
14
+ from ..i18n import _
15
+ from ..store import open_store
16
+ from ..utils import build_help_epilog, confirm, format_cost, format_ts, parse_date, parse_time_spec
16
17
 
17
18
  _DELETE_EXAMPLES = [
18
19
  ("<session_id>", "help.delete.e0"),
@@ -322,6 +323,10 @@ def _a(args, name: str, default=None):
322
323
 
323
324
 
324
325
  def run(args, db) -> int:
326
+ store = open_store(db)
327
+ if store.schema.read_only:
328
+ print(_("schema.unsupported_write"))
329
+ return 1
325
330
  if _a(args, "interactive", False):
326
331
  return _delete_interactive(db, args)
327
332