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.
- {opencode_db-0.7.2/opencode_db.egg-info → opencode_db-0.8.0}/PKG-INFO +6 -2
- {opencode_db-0.7.2 → opencode_db-0.8.0}/README.md +95 -51
- {opencode_db-0.7.2 → opencode_db-0.8.0/docs}/README_PYPI.md +5 -1
- opencode_db-0.8.0/opencode_db/__init__.py +5 -0
- opencode_db-0.8.0/opencode_db/__main__.py +6 -0
- {opencode_db-0.7.2 → opencode_db-0.8.0/opencode_db}/cli.py +5 -5
- {opencode_db-0.7.2 → opencode_db-0.8.0/opencode_db}/commands/__init__.py +20 -16
- {opencode_db-0.7.2 → opencode_db-0.8.0/opencode_db/commands}/cmd_costs.py +24 -55
- {opencode_db-0.7.2 → opencode_db-0.8.0/opencode_db/commands}/cmd_delete.py +8 -3
- {opencode_db-0.7.2 → opencode_db-0.8.0/opencode_db/commands}/cmd_export.py +27 -33
- {opencode_db-0.7.2 → opencode_db-0.8.0/opencode_db/commands}/cmd_help.py +10 -2
- {opencode_db-0.7.2 → opencode_db-0.8.0/opencode_db/commands}/cmd_info.py +16 -25
- {opencode_db-0.7.2 → opencode_db-0.8.0/opencode_db/commands}/cmd_list.py +15 -47
- {opencode_db-0.7.2 → opencode_db-0.8.0/opencode_db/commands}/cmd_projects.py +6 -16
- {opencode_db-0.7.2 → opencode_db-0.8.0/opencode_db/commands}/cmd_prune.py +3 -3
- opencode_db-0.8.0/opencode_db/commands/cmd_schema.py +56 -0
- opencode_db-0.8.0/opencode_db/commands/cmd_search.py +71 -0
- {opencode_db-0.7.2 → opencode_db-0.8.0/opencode_db/commands}/cmd_stats.py +15 -42
- {opencode_db-0.7.2 → opencode_db-0.8.0/opencode_db/commands}/cmd_todos.py +14 -30
- opencode_db-0.8.0/opencode_db/commands/cmd_tree.py +101 -0
- {opencode_db-0.7.2 → opencode_db-0.8.0/opencode_db/commands}/cmd_vacuum.py +13 -6
- {opencode_db-0.7.2 → opencode_db-0.8.0/opencode_db/commands}/cmd_view.py +34 -37
- opencode_db-0.8.0/opencode_db/config.py +40 -0
- {opencode_db-0.7.2 → opencode_db-0.8.0/opencode_db}/db.py +33 -46
- {opencode_db-0.7.2 → opencode_db-0.8.0/opencode_db}/formatters.py +2 -2
- {opencode_db-0.7.2 → opencode_db-0.8.0/opencode_db}/i18n.py +35 -1
- opencode_db-0.8.0/opencode_db/schema.py +243 -0
- opencode_db-0.8.0/opencode_db/schema_manifest.json +500 -0
- opencode_db-0.8.0/opencode_db/store/__init__.py +20 -0
- opencode_db-0.8.0/opencode_db/store/base.py +286 -0
- opencode_db-0.8.0/opencode_db/store/v1.py +253 -0
- opencode_db-0.8.0/opencode_db/store/v2.py +459 -0
- {opencode_db-0.7.2 → opencode_db-0.8.0/opencode_db}/utils.py +1 -1
- {opencode_db-0.7.2 → opencode_db-0.8.0/opencode_db.egg-info}/PKG-INFO +6 -2
- opencode_db-0.8.0/opencode_db.egg-info/SOURCES.txt +50 -0
- opencode_db-0.8.0/opencode_db.egg-info/entry_points.txt +2 -0
- opencode_db-0.8.0/opencode_db.egg-info/top_level.txt +1 -0
- {opencode_db-0.7.2 → opencode_db-0.8.0}/pyproject.toml +9 -14
- {opencode_db-0.7.2 → opencode_db-0.8.0}/tests/test_commands.py +81 -81
- {opencode_db-0.7.2 → opencode_db-0.8.0}/tests/test_db.py +1 -1
- {opencode_db-0.7.2 → opencode_db-0.8.0}/tests/test_formatters.py +1 -1
- {opencode_db-0.7.2 → opencode_db-0.8.0}/tests/test_i18n.py +2 -2
- {opencode_db-0.7.2 → opencode_db-0.8.0}/tests/test_runtime.py +1 -1
- opencode_db-0.8.0/tests/test_schema.py +133 -0
- opencode_db-0.8.0/tests/test_store_v2.py +282 -0
- {opencode_db-0.7.2 → opencode_db-0.8.0}/tests/test_utils.py +1 -1
- opencode_db-0.7.2/cmd_search.py +0 -124
- opencode_db-0.7.2/cmd_tree.py +0 -110
- opencode_db-0.7.2/config.py +0 -16
- opencode_db-0.7.2/opencode_db.egg-info/SOURCES.txt +0 -39
- opencode_db-0.7.2/opencode_db.egg-info/entry_points.txt +0 -2
- opencode_db-0.7.2/opencode_db.egg-info/top_level.txt +0 -23
- {opencode_db-0.7.2 → opencode_db-0.8.0}/LICENSE +0 -0
- {opencode_db-0.7.2 → opencode_db-0.8.0/opencode_db}/markdown_to_ansi.py +0 -0
- {opencode_db-0.7.2 → opencode_db-0.8.0/opencode_db}/runtime.py +0 -0
- {opencode_db-0.7.2 → opencode_db-0.8.0}/opencode_db.egg-info/dependency_links.txt +0 -0
- {opencode_db-0.7.2 → opencode_db-0.8.0}/opencode_db.egg-info/requires.txt +0 -0
- {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.
|
|
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
|
[](https://www.python.org/downloads/)
|
|
20
|
-
[](LICENSE)
|
|
20
|
+
[](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
|
-
|
|
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
|
|
114
|
-
sys.exit(
|
|
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
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
│
|
|
514
|
-
├──
|
|
515
|
-
├──
|
|
516
|
-
├──
|
|
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
|
-
├──
|
|
519
|
-
├──
|
|
520
|
-
|
|
521
|
-
├──
|
|
522
|
-
|
|
523
|
-
├──
|
|
524
|
-
├──
|
|
525
|
-
|
|
526
|
-
├──
|
|
527
|
-
|
|
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
|
|
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.
|
|
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
|
|
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 +
|
|
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
|
|
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
|
[](https://www.python.org/downloads/)
|
|
4
|
-
[](LICENSE)
|
|
4
|
+
[](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.
|
|
@@ -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
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
|
9
|
-
from i18n import _
|
|
10
|
-
from
|
|
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(
|
|
39
|
+
return _show_session(store, args.session_id, args.json)
|
|
38
40
|
|
|
39
41
|
if args.total:
|
|
40
|
-
return _show_total(
|
|
42
|
+
return _show_total(store, args.project, args.json)
|
|
41
43
|
|
|
42
|
-
return _show_list(
|
|
44
|
+
return _show_list(store, args.project, args.limit, args.json)
|
|
43
45
|
|
|
44
46
|
|
|
45
|
-
def _show_session(
|
|
47
|
+
def _show_session(store, session_id, as_json) -> int:
|
|
46
48
|
try:
|
|
47
|
-
info = get_session_info(
|
|
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(
|
|
94
|
-
|
|
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(
|
|
147
|
-
|
|
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
|
|
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
|
|