opencode-manager 0.3.0 → 0.4.0
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.
- package/PROJECT-SUMMARY.md +104 -24
- package/README.md +335 -7
- package/bun.lock +17 -1
- package/manage_opencode_projects.py +71 -66
- package/package.json +6 -3
- package/src/bin/opencode-manager.ts +133 -3
- package/src/cli/backup.ts +324 -0
- package/src/cli/commands/chat.ts +322 -0
- package/src/cli/commands/projects.ts +222 -0
- package/src/cli/commands/sessions.ts +495 -0
- package/src/cli/commands/tokens.ts +168 -0
- package/src/cli/commands/tui.ts +36 -0
- package/src/cli/errors.ts +259 -0
- package/src/cli/formatters/json.ts +184 -0
- package/src/cli/formatters/ndjson.ts +71 -0
- package/src/cli/formatters/table.ts +837 -0
- package/src/cli/index.ts +169 -0
- package/src/cli/output.ts +661 -0
- package/src/cli/resolvers.ts +249 -0
- package/src/lib/clipboard.ts +37 -0
- package/src/lib/opencode-data.ts +380 -1
- package/src/lib/search.ts +170 -0
- package/src/{opencode-tui.tsx → tui/app.tsx} +739 -105
- package/src/tui/args.ts +92 -0
- package/src/tui/index.tsx +46 -0
package/PROJECT-SUMMARY.md
CHANGED
|
@@ -5,15 +5,47 @@ Overview
|
|
|
5
5
|
--------
|
|
6
6
|
- Purpose: Inspect, filter, and clean OpenCode metadata stored on disk.
|
|
7
7
|
- Scope: Lists projects and sessions from local storage; supports filtering, search, interactive selection and deletion, and quick navigation between views.
|
|
8
|
-
-
|
|
8
|
+
- Dual interface: Terminal UI (TUI) built with @opentui/react for interactive use, plus a Commander-based CLI for scripting and automation.
|
|
9
9
|
|
|
10
10
|
Architecture
|
|
11
11
|
------------
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
12
|
+
The codebase follows a dual-mode architecture with shared libraries:
|
|
13
|
+
|
|
14
|
+
### Entry Points
|
|
15
|
+
- `src/bin/opencode-manager.ts` — Main entrypoint; routes to CLI or TUI based on subcommand.
|
|
16
|
+
- CLI subcommands: `projects`, `sessions`, `chat`, `tokens` → dynamic import of CLI module
|
|
17
|
+
- TUI mode: no subcommand, or explicit `tui` subcommand → dynamic import of TUI module
|
|
18
|
+
|
|
19
|
+
### CLI Module (`src/cli/`)
|
|
20
|
+
- `index.ts` — Commander program with global options (`--root`, `--format`, `--limit`, `--sort`, `--yes`, `--dry-run`, `--quiet`, `--clipboard`, `--backup-dir`); exports `runCLI(args)`.
|
|
21
|
+
- `commands/` — Subcommand implementations:
|
|
22
|
+
- `projects.ts` — `list`, `delete` with dry-run/backup support
|
|
23
|
+
- `sessions.ts` — `list`, `delete`, `rename`, `move`, `copy`
|
|
24
|
+
- `chat.ts` — `list`, `show`, `search` with clipboard support
|
|
25
|
+
- `tokens.ts` — `session`, `project`, `global` token summaries
|
|
26
|
+
- `tui.ts` — Launches TUI from CLI context
|
|
27
|
+
- `formatters/` — Output formatters:
|
|
28
|
+
- `json.ts` — JSON with envelope (`{ok, data, meta}`)
|
|
29
|
+
- `ndjson.ts` — Newline-delimited JSON for streaming
|
|
30
|
+
- `table.ts` — Column-aligned tables with truncation
|
|
31
|
+
- `output.ts` — Format selector routing by `--format` flag
|
|
32
|
+
- `errors.ts` — Exit codes (0-4), error classes, validation helpers
|
|
33
|
+
- `resolvers.ts` — ID resolution with exact/prefix matching
|
|
34
|
+
- `backup.ts` — Pre-deletion backup to timestamped directories
|
|
35
|
+
|
|
36
|
+
### TUI Module (`src/tui/`)
|
|
37
|
+
- `app.tsx` — Main TUI app with Projects, Sessions, Chat panels
|
|
38
|
+
- `index.tsx` — Exports `launchTUI(options)`, `bootstrap(args)`
|
|
39
|
+
- `args.ts` — TUI-specific arg parsing (`--root`, `--help`)
|
|
40
|
+
|
|
41
|
+
### Shared Libraries (`src/lib/`)
|
|
42
|
+
- `opencode-data.ts` — Data layer: load/save metadata, compute tokens, filtering, formatting
|
|
43
|
+
- `search.ts` — Fuzzy search via fast-fuzzy (sessions) and tokenized search (projects)
|
|
44
|
+
- `clipboard.ts` — Cross-platform clipboard (`pbcopy`/`xclip`)
|
|
45
|
+
|
|
46
|
+
### Other Files
|
|
47
|
+
- `manage_opencode_projects.py` — Legacy wrapper; routes CLI/TUI via `src/bin/opencode-manager.ts`
|
|
48
|
+
- `opencode-gen.sh` — Spec diff script for OpenCode JSON specs
|
|
17
49
|
|
|
18
50
|
Metadata Layout & Helpers
|
|
19
51
|
-------------------------
|
|
@@ -37,6 +69,8 @@ Key Features
|
|
|
37
69
|
- Rename sessions inline (Shift+R) with validation.
|
|
38
70
|
- Move sessions to another project (M) with project selector.
|
|
39
71
|
- Copy sessions to another project (P) with new session ID generation.
|
|
72
|
+
- View chat history (V) with message navigation and clipboard support.
|
|
73
|
+
- Search chat content (F) across sessions with fuzzy matching.
|
|
40
74
|
- Projects ↔ Sessions workflow
|
|
41
75
|
- Pressing Enter on a project jumps to the Sessions tab with the project filter set; status text confirms the active filter.
|
|
42
76
|
- `C` clears the filter (and notifies the user) so global searches go back to all sessions.
|
|
@@ -53,6 +87,7 @@ Key Features
|
|
|
53
87
|
|
|
54
88
|
Work Completed
|
|
55
89
|
--------------
|
|
90
|
+
### TUI Features
|
|
56
91
|
- Switched Projects list labels to show path instead of project ID; kept ID in the details panel.
|
|
57
92
|
- Sessions list uses session title prominently; added title to details; updated onSelect/Enter status lines to show title and ID.
|
|
58
93
|
- Redesigned Help screen into two columns with color-coded sections and key chips; removed wall-of-text effect.
|
|
@@ -69,36 +104,79 @@ Work Completed
|
|
|
69
104
|
- Added session move feature (M): select target project, relocate session JSON, update projectID field.
|
|
70
105
|
- Added session copy feature (P): select target project, create new session with generated ID, preserve original.
|
|
71
106
|
|
|
107
|
+
### CLI Implementation (Phase 1-4)
|
|
108
|
+
- Created Commander-based CLI with global options and subcommand routing.
|
|
109
|
+
- Refactored entrypoint to route CLI vs TUI via dynamic imports.
|
|
110
|
+
- Extracted shared libraries (`clipboard.ts`, `search.ts`) from TUI for CLI reuse.
|
|
111
|
+
- Implemented output formatters: JSON with envelope, NDJSON for streaming, table with truncation.
|
|
112
|
+
- Projects commands: `list` (with `--missing-only`, `--search`), `delete` (with `--dry-run`, `--backup-dir`).
|
|
113
|
+
- Sessions commands: `list` (with `--project`, `--search`, fuzzy matching), `delete`, `rename`, `move`, `copy`.
|
|
114
|
+
- Chat commands: `list` (with `--include-parts`), `show` (by `--message` or `--index`, with `--clipboard`), `search`.
|
|
115
|
+
- Tokens commands: `session`, `project`, `global` summaries with breakdown tables.
|
|
116
|
+
- Error handling with typed exit codes (0-4) and consistent error formatting.
|
|
117
|
+
- ID resolution helpers with exact and prefix matching.
|
|
118
|
+
- Pre-deletion backup to timestamped directories.
|
|
119
|
+
- Comprehensive test suite: 350+ tests covering formatters, commands, resolvers, errors, exit codes.
|
|
120
|
+
|
|
72
121
|
How To Run
|
|
73
122
|
----------
|
|
74
|
-
|
|
75
|
-
-
|
|
76
|
-
-
|
|
77
|
-
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
-
|
|
82
|
-
-
|
|
123
|
+
### TUI Mode (Interactive)
|
|
124
|
+
- Zero-install: `bunx opencode-manager [--root /path/to/storage]` (preferred)
|
|
125
|
+
- Local dev: `bun run tui [-- --root /path/to/storage]`
|
|
126
|
+
- Legacy launcher: `./manage_opencode_projects.py [--bun /path/to/bun]`
|
|
127
|
+
- TUI help: `bunx opencode-manager --help` shows key bindings
|
|
128
|
+
|
|
129
|
+
### CLI Mode (Scripting)
|
|
130
|
+
- Projects: `bunx opencode-manager projects list --format json`
|
|
131
|
+
- Sessions: `bunx opencode-manager sessions list --project <id> --limit 10`
|
|
132
|
+
- Chat: `bunx opencode-manager chat search --query "fix bug" --format ndjson`
|
|
133
|
+
- Tokens: `bunx opencode-manager tokens global --format table`
|
|
134
|
+
- Delete with backup: `bunx opencode-manager sessions delete --session <id> --yes --backup-dir ./backups`
|
|
135
|
+
- Dry run: `bunx opencode-manager projects delete --id <id> --dry-run`
|
|
136
|
+
|
|
137
|
+
### Global CLI Options
|
|
138
|
+
- `--root <path>` — Metadata store root (default: `~/.local/share/opencode`)
|
|
139
|
+
- `--format <json|ndjson|table>` — Output format (default: `table`)
|
|
140
|
+
- `--limit <n>` — Max records (default: 200)
|
|
141
|
+
- `--sort <updated|created>` — Sort order (default: `updated`)
|
|
142
|
+
- `--yes` — Skip confirmation for destructive ops
|
|
143
|
+
- `--dry-run` — Preview changes without executing
|
|
144
|
+
- `--quiet` — Suppress non-essential output
|
|
145
|
+
- `--clipboard` — Copy output to clipboard
|
|
146
|
+
- `--backup-dir <path>` — Backup before deletion
|
|
147
|
+
|
|
148
|
+
### Exit Codes
|
|
149
|
+
- 0: Success
|
|
150
|
+
- 1: Internal error
|
|
151
|
+
- 2: Usage/validation error (missing `--yes`, bad args)
|
|
152
|
+
- 3: Resource not found (invalid project/session/message ID)
|
|
153
|
+
- 4: File operation error (backup/delete failure)
|
|
154
|
+
|
|
155
|
+
### TUI Keys
|
|
156
|
+
- Global: `Tab`/`1`/`2` switch tabs, `/` search, `X` clear search, `R` reload, `Q` quit, `?/H` help
|
|
157
|
+
- Projects: `Space` select, `A` select all, `M` toggle missing, `D` delete, `Enter` view sessions, `Esc` clear selection
|
|
158
|
+
- Sessions: `Space` select, `A` select all, `S` sort, `V` view chat, `F` search chats, `Shift+R` rename, `M` move, `P` copy, `Y` copy ID, `C` clear filter, `D` delete, `Enter` details, `Esc` clear selection
|
|
159
|
+
|
|
160
|
+
### Optional
|
|
161
|
+
- tmux: `tmux new -s opencode-manager 'bun run tui'`
|
|
83
162
|
|
|
84
163
|
Packaging & Publish Checklist
|
|
85
164
|
-----------------------------
|
|
86
|
-
1. Install dependencies with `bun install` (Bun v1.
|
|
165
|
+
1. Install dependencies with `bun install` (Bun v1.3+ only).
|
|
87
166
|
2. Type-check via `bun run typecheck` (runs `tsc --noEmit`).
|
|
88
167
|
3. Update the version in `package.json` as needed.
|
|
89
168
|
4. Run `npm publish` (package exposes the Bun-native `opencode-manager` bin with public access).
|
|
90
169
|
|
|
91
170
|
Outstanding Recommendations (Not Yet Implemented)
|
|
92
171
|
-------------------------------------------------
|
|
93
|
-
-
|
|
172
|
+
- TUI polish
|
|
94
173
|
- Colorize project state in list labels (e.g., green for present, red for missing, gray for unknown).
|
|
95
174
|
- Show a small timestamp snippet for Projects rows (created).
|
|
96
175
|
- Add tiny icons or color accents to distinguish created vs updated descriptions in Sessions.
|
|
97
176
|
- Add per-view mini legends with colored key chips under the panels (Projects/Sessions), consistent with the Help styling.
|
|
98
|
-
- Show filtered counts (e.g.,
|
|
177
|
+
- Show filtered counts (e.g., "Showing X of Y").
|
|
99
178
|
- Search enhancements
|
|
100
|
-
- Optional fuzzy matching
|
|
101
|
-
- Persist search and sort preferences per tab (and optionally per project filter) during the app run.
|
|
179
|
+
- Optional fuzzy matching toggle in TUI; persist search and sort preferences per tab.
|
|
102
180
|
- Save last-used search/sort to a small state file and restore on next launch.
|
|
103
181
|
- Accessibility & layout
|
|
104
182
|
- Ensure all active/inactive/focused states have adequate contrast and consistent highlight styles.
|
|
@@ -106,10 +184,12 @@ Outstanding Recommendations (Not Yet Implemented)
|
|
|
106
184
|
- Performance
|
|
107
185
|
- Debounce UI reactions to large search queries; short-circuit expensive filters when query is empty.
|
|
108
186
|
- Testing
|
|
109
|
-
- Add
|
|
110
|
-
- Add basic snapshot/E2E tests for rendering
|
|
111
|
-
-
|
|
112
|
-
-
|
|
187
|
+
- Add integration tests for CLI commands with real fixture data.
|
|
188
|
+
- Add basic snapshot/E2E tests for TUI rendering (if headless renderer available for @opentui).
|
|
189
|
+
- CLI enhancements
|
|
190
|
+
- Shell completion scripts (bash/zsh/fish).
|
|
191
|
+
- `--json-lines` alias for `--format ndjson` compatibility.
|
|
192
|
+
- Template/profile support for common option combinations.
|
|
113
193
|
|
|
114
194
|
Notes
|
|
115
195
|
-----
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
# OpenCode Metadata Manager
|
|
5
5
|
|
|
6
|
-
Terminal UI for inspecting, filtering, and pruning OpenCode metadata stored on disk. The app is written in TypeScript, runs on Bun, and renders with [`@opentui/react`](https://github.com/
|
|
6
|
+
Terminal UI for inspecting, filtering, and pruning OpenCode metadata stored on disk. The app is written in TypeScript, runs on Bun, and renders with [`@opentui/react`](https://github.com/sst/opentui).
|
|
7
7
|
|
|
8
8
|
## Screenshots
|
|
9
9
|
|
|
@@ -23,7 +23,9 @@ Terminal UI for inspecting, filtering, and pruning OpenCode metadata stored on d
|
|
|
23
23
|
- List both OpenCode projects and sessions from a local metadata root.
|
|
24
24
|
- Filter by "missing only", bulk-select, and delete metadata safely.
|
|
25
25
|
- Jump from a project directly to its sessions and keep contextual filters.
|
|
26
|
-
-
|
|
26
|
+
- **Fuzzy search** across session titles and metadata (`/` to focus, results ranked by relevance).
|
|
27
|
+
- **View session chat history** with full conversation context (`V` to open viewer).
|
|
28
|
+
- **Search across all chat content** in sessions within a project (`F` to search).
|
|
27
29
|
- Rename sessions inline (`Shift+R`) with title validation.
|
|
28
30
|
- Move sessions between projects (`M`) preserving session ID.
|
|
29
31
|
- Copy sessions to other projects (`P`) with new session ID generation.
|
|
@@ -56,7 +58,7 @@ The TUI displays token telemetry from OpenCode's stored message data at three le
|
|
|
56
58
|
- Large datasets are handled with lazy computation to avoid UI freezes.
|
|
57
59
|
|
|
58
60
|
## Requirements
|
|
59
|
-
- [Bun](https://bun.sh) **1.
|
|
61
|
+
- [Bun](https://bun.sh) **1.3.0+** (developed/tested on 1.3.x).
|
|
60
62
|
- A node-compatible terminal (truecolor improves readability but is optional).
|
|
61
63
|
|
|
62
64
|
## Installation
|
|
@@ -73,6 +75,13 @@ bunx opencode-manager --help
|
|
|
73
75
|
The repository ships with a focused `.gitignore`, keeping `node_modules/`, caches, and logs out of Git history.
|
|
74
76
|
|
|
75
77
|
## Usage
|
|
78
|
+
|
|
79
|
+
The manager provides both a Terminal UI (TUI) and a scriptable CLI interface.
|
|
80
|
+
|
|
81
|
+
### Terminal UI (TUI)
|
|
82
|
+
|
|
83
|
+
The TUI is the default interface when no subcommand is provided:
|
|
84
|
+
|
|
76
85
|
```bash
|
|
77
86
|
# Preferred: zero-install command
|
|
78
87
|
bunx opencode-manager --root ~/.local/share/opencode
|
|
@@ -85,9 +94,326 @@ bun run tui -- --root ~/.local/share/opencode
|
|
|
85
94
|
```
|
|
86
95
|
|
|
87
96
|
Keyboard reference:
|
|
88
|
-
- **Global**: `Tab`/`1`/`2` switch tabs, `/` search, `X` clear search, `R` reload, `Q` quit,
|
|
89
|
-
- **Projects**: `Space` toggle selection, `A` select all, `M` missing-only filter, `D` delete, `Enter` jump to Sessions.
|
|
90
|
-
- **Sessions**: `Space` select, `S` toggle sort, `D` delete, `Y` copy ID, `Shift+R` rename, `M` move
|
|
97
|
+
- **Global**: `Tab`/`1`/`2` switch tabs, `/` search (fuzzy), `X` clear search, `R` reload, `Q` quit, `?/H` help.
|
|
98
|
+
- **Projects**: `Space` toggle selection, `A` select all, `M` missing-only filter, `D` delete, `Enter` jump to Sessions, `Esc` clear selection.
|
|
99
|
+
- **Sessions**: `Space` select, `A` select all, `S` toggle sort, `V` view chat, `F` search chats, `D` delete, `Y` copy ID, `Shift+R` rename, `M` move, `P` copy, `C` clear filter, `Enter` details, `Esc` clear selection.
|
|
100
|
+
- **Chat Search**: Type query + `Enter` to search, `Up/Down` navigate, `Enter` opens result, `Esc` close.
|
|
101
|
+
- **Chat Viewer**: `Esc` close, `Up/Down` navigate, `PgUp/PgDn` jump 10, `Home/End` first/last, `Y` copy message.
|
|
102
|
+
|
|
103
|
+
### Command Line Interface (CLI)
|
|
104
|
+
|
|
105
|
+
The CLI provides scriptable access to all management operations. Use subcommands to list, search, and modify metadata.
|
|
106
|
+
|
|
107
|
+
#### Global Options
|
|
108
|
+
|
|
109
|
+
| Option | Default | Description |
|
|
110
|
+
|--------|---------|-------------|
|
|
111
|
+
| `-r, --root <path>` | `~/.local/share/opencode` | Root path to OpenCode metadata store |
|
|
112
|
+
| `-f, --format <fmt>` | `table` | Output format: `json`, `ndjson`, or `table` |
|
|
113
|
+
| `-l, --limit <n>` | `200` | Maximum number of records to return |
|
|
114
|
+
| `--sort <order>` | `updated` | Sort order: `updated` or `created` |
|
|
115
|
+
| `-y, --yes` | `false` | Skip confirmation prompts for destructive operations |
|
|
116
|
+
| `-n, --dry-run` | `false` | Preview changes without executing |
|
|
117
|
+
| `-q, --quiet` | `false` | Suppress non-essential output |
|
|
118
|
+
| `-c, --clipboard` | `false` | Copy output to clipboard |
|
|
119
|
+
| `--backup-dir <path>` | — | Directory for backup copies before deletion |
|
|
120
|
+
|
|
121
|
+
#### Commands Overview
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
opencode-manager
|
|
125
|
+
├── projects
|
|
126
|
+
│ ├── list List projects (--missing-only, --search)
|
|
127
|
+
│ └── delete Delete project metadata (--id, --yes, --dry-run, --backup-dir)
|
|
128
|
+
├── sessions
|
|
129
|
+
│ ├── list List sessions (--project, --search)
|
|
130
|
+
│ ├── delete Delete session metadata (--session, --yes, --dry-run, --backup-dir)
|
|
131
|
+
│ ├── rename Rename a session (--session, --title)
|
|
132
|
+
│ ├── move Move session to another project (--session, --to)
|
|
133
|
+
│ └── copy Copy session to another project (--session, --to)
|
|
134
|
+
├── chat
|
|
135
|
+
│ ├── list List messages in a session (--session, --include-parts)
|
|
136
|
+
│ ├── show Show a specific message (--session, --message or --index, --clipboard)
|
|
137
|
+
│ └── search Search chat content across sessions (--query, --project)
|
|
138
|
+
├── tokens
|
|
139
|
+
│ ├── session Show token usage for a session (--session)
|
|
140
|
+
│ ├── project Show token usage for a project (--project)
|
|
141
|
+
│ └── global Show global token usage
|
|
142
|
+
└── tui Launch the Terminal UI
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
#### TUI Subcommand
|
|
146
|
+
|
|
147
|
+
The `tui` subcommand explicitly launches the Terminal UI. This is equivalent to running `opencode-manager` with no subcommand:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
# These are equivalent:
|
|
151
|
+
opencode-manager
|
|
152
|
+
opencode-manager tui
|
|
153
|
+
opencode-manager tui --root ~/.local/share/opencode
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Use the explicit `tui` subcommand when you want to be clear about intent in scripts or when combining with other options. Note that `tui --help` shows the TUI help screen (key bindings), not Commander CLI help.
|
|
157
|
+
|
|
158
|
+
#### Help System
|
|
159
|
+
|
|
160
|
+
The manager uses a dual help system depending on context:
|
|
161
|
+
|
|
162
|
+
| Command | Help Type | Content |
|
|
163
|
+
|---------|-----------|---------|
|
|
164
|
+
| `opencode-manager --help` | TUI help | Key bindings and TUI usage |
|
|
165
|
+
| `opencode-manager -h` | TUI help | Same as `--help` |
|
|
166
|
+
| `opencode-manager tui --help` | TUI help | Key bindings (routes to TUI help) |
|
|
167
|
+
| `opencode-manager projects --help` | CLI help | Commander subcommand help |
|
|
168
|
+
| `opencode-manager sessions --help` | CLI help | Commander subcommand help |
|
|
169
|
+
| `opencode-manager chat --help` | CLI help | Commander subcommand help |
|
|
170
|
+
| `opencode-manager tokens --help` | CLI help | Commander subcommand help |
|
|
171
|
+
|
|
172
|
+
**Why?** The root command defaults to launching the TUI, so `--help` shows TUI-relevant information (key bindings). CLI subcommands use standard Commander.js help showing options and usage.
|
|
173
|
+
|
|
174
|
+
To see all CLI subcommands, use any subcommand with `--help`:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
# Shows TUI key bindings
|
|
178
|
+
opencode-manager --help
|
|
179
|
+
|
|
180
|
+
# Shows CLI subcommand help with options
|
|
181
|
+
opencode-manager projects --help
|
|
182
|
+
opencode-manager projects list --help
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
#### Output Format Examples
|
|
186
|
+
|
|
187
|
+
The CLI supports three output formats via `--format`:
|
|
188
|
+
|
|
189
|
+
**Table (default)** — Human-readable columnar output:
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
$ bunx opencode-manager projects list --limit 3
|
|
193
|
+
|
|
194
|
+
# State Path Project ID Created
|
|
195
|
+
---- ----- -------------------------------------------------- ------------------------ ----------------
|
|
196
|
+
1 ✓ /home/user/repos/my-app a1b2c3d4e5f6g7h8i9j0k1l2 2026-01-04 09:20
|
|
197
|
+
2 ✓ /home/user/repos/api-server b2c3d4e5f6g7h8i9j0k1l2m3 2026-01-03 14:15
|
|
198
|
+
3 ✗ /home/user/repos/deleted-project c3d4e5f6g7h8i9j0k1l2m3n4 2025-12-28 10:30
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**JSON** — Structured output with metadata envelope:
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
$ bunx opencode-manager sessions list --project prj_abc123 --format json --limit 2
|
|
205
|
+
|
|
206
|
+
{
|
|
207
|
+
"ok": true,
|
|
208
|
+
"data": [
|
|
209
|
+
{
|
|
210
|
+
"index": 1,
|
|
211
|
+
"sessionId": "sess_xyz789",
|
|
212
|
+
"projectId": "prj_abc123",
|
|
213
|
+
"directory": "/home/user/repos/my-app",
|
|
214
|
+
"title": "Refactor auth module",
|
|
215
|
+
"version": "1.1.4",
|
|
216
|
+
"updatedAt": "2026-01-05T14:32:00.000Z",
|
|
217
|
+
"createdAt": "2026-01-03T09:15:00.000Z"
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"index": 2,
|
|
221
|
+
"sessionId": "sess_uvw456",
|
|
222
|
+
"projectId": "prj_abc123",
|
|
223
|
+
"directory": "/home/user/repos/my-app",
|
|
224
|
+
"title": "Add unit tests",
|
|
225
|
+
"version": "1.1.4",
|
|
226
|
+
"updatedAt": "2026-01-04T16:45:00.000Z",
|
|
227
|
+
"createdAt": "2026-01-02T11:20:00.000Z"
|
|
228
|
+
}
|
|
229
|
+
],
|
|
230
|
+
"meta": {
|
|
231
|
+
"count": 2,
|
|
232
|
+
"limit": 2
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
JSON output auto-detects your terminal: pretty-printed with indentation when output goes to a TTY, compact single-line when piped to another command.
|
|
238
|
+
|
|
239
|
+
The `meta` object contains:
|
|
240
|
+
- `count` — Number of items in the `data` array
|
|
241
|
+
- `limit` — The limit that was applied (if `--limit` was specified)
|
|
242
|
+
|
|
243
|
+
**NDJSON** — Newline-delimited JSON for streaming/piping:
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
$ bunx opencode-manager sessions list --format ndjson --limit 2
|
|
247
|
+
|
|
248
|
+
{"index":1,"sessionId":"ses_abc123","projectId":"prj_xyz789","title":"Feature implementation","createdAt":"2026-01-06T10:30:00.000Z"}
|
|
249
|
+
{"index":2,"sessionId":"ses_def456","projectId":"prj_xyz789","title":"Bug fix session","createdAt":"2026-01-06T11:15:00.000Z"}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Each line is a complete JSON object, ideal for piping to `jq` or line-by-line processing. For single-record commands like `tokens global`, NDJSON outputs one line with the same structure as the JSON `data` field.
|
|
253
|
+
|
|
254
|
+
**Piping examples:**
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
# Count sessions per project
|
|
258
|
+
bunx opencode-manager sessions list --format ndjson | jq -s 'group_by(.projectId) | map({project: .[0].projectId, count: length})'
|
|
259
|
+
|
|
260
|
+
# Get all session IDs as plain text
|
|
261
|
+
bunx opencode-manager sessions list --format json | jq -r '.data[].sessionId'
|
|
262
|
+
|
|
263
|
+
# Export chat history to file
|
|
264
|
+
bunx opencode-manager chat list --session sess_xyz789 --include-parts --format json > chat-export.json
|
|
265
|
+
|
|
266
|
+
# Dry-run delete to preview affected files
|
|
267
|
+
bunx opencode-manager projects delete --id prj_old --dry-run --format json
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
#### Token Output Format
|
|
271
|
+
|
|
272
|
+
Token commands (`tokens session`, `tokens project`, `tokens global`) return structured summaries of token usage.
|
|
273
|
+
|
|
274
|
+
**Session tokens** — Single session summary with `kind` discriminator:
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
$ bunx opencode-manager tokens session --session sess_xyz789 --format json
|
|
278
|
+
|
|
279
|
+
# When token data is available (kind: "known"):
|
|
280
|
+
{
|
|
281
|
+
"ok": true,
|
|
282
|
+
"data": {
|
|
283
|
+
"kind": "known",
|
|
284
|
+
"tokens": {
|
|
285
|
+
"input": 12500,
|
|
286
|
+
"output": 8750,
|
|
287
|
+
"reasoning": 2100,
|
|
288
|
+
"cacheRead": 4200,
|
|
289
|
+
"cacheWrite": 950,
|
|
290
|
+
"total": 28500
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
# When token data is unavailable (kind: "unknown"):
|
|
296
|
+
{
|
|
297
|
+
"ok": true,
|
|
298
|
+
"data": {
|
|
299
|
+
"kind": "unknown",
|
|
300
|
+
"reason": "no_messages"
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
The `reason` field indicates why tokens are unavailable:
|
|
306
|
+
- `"missing"` — Session metadata file not found
|
|
307
|
+
- `"parse_error"` — Token data couldn't be parsed
|
|
308
|
+
- `"no_messages"` — Session has no messages with token data
|
|
309
|
+
|
|
310
|
+
**Project/Global tokens** — Aggregate summary across multiple sessions:
|
|
311
|
+
|
|
312
|
+
```bash
|
|
313
|
+
$ bunx opencode-manager tokens project --project prj_abc123 --format json
|
|
314
|
+
|
|
315
|
+
{
|
|
316
|
+
"ok": true,
|
|
317
|
+
"data": {
|
|
318
|
+
"total": {
|
|
319
|
+
"kind": "known",
|
|
320
|
+
"tokens": { "input": 125000, "output": 98000, "reasoning": 32000, "cacheRead": 15000, "cacheWrite": 6500, "total": 276500 }
|
|
321
|
+
},
|
|
322
|
+
"knownOnly": { "input": 125000, "output": 98000, "reasoning": 32000, "cacheRead": 15000, "cacheWrite": 6500, "total": 276500 },
|
|
323
|
+
"unknownSessions": 2
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
Aggregate summary fields:
|
|
329
|
+
- `total` — Combined `TokenSummary` (same structure as session tokens)
|
|
330
|
+
- `knownOnly` — Token breakdown from sessions with available data only (omitted if all unknown)
|
|
331
|
+
- `unknownSessions` — Count of sessions where token data was unavailable
|
|
332
|
+
|
|
333
|
+
**Note:** The `tokens` commands require exact IDs (no prefix matching). Use full session/project IDs as shown in `sessions list` or `projects list` output.
|
|
334
|
+
|
|
335
|
+
#### Exit Codes
|
|
336
|
+
|
|
337
|
+
| Code | Meaning |
|
|
338
|
+
|------|---------|
|
|
339
|
+
| 0 | Success |
|
|
340
|
+
| 1 | General error |
|
|
341
|
+
| 2 | Usage error (missing required options, invalid arguments) |
|
|
342
|
+
| 3 | Resource not found (invalid project/session/message ID) |
|
|
343
|
+
| 4 | File operation error (backup or delete failure) |
|
|
344
|
+
|
|
345
|
+
#### ID Resolution
|
|
346
|
+
|
|
347
|
+
Most commands accept ID prefixes for convenience. The CLI will match the prefix to a unique ID:
|
|
348
|
+
|
|
349
|
+
```bash
|
|
350
|
+
# Full ID
|
|
351
|
+
opencode-manager sessions delete --session sess_01JGNPE16DT1JX1YA8KTPMDRW3
|
|
352
|
+
|
|
353
|
+
# Prefix match (if unique)
|
|
354
|
+
opencode-manager sessions delete --session sess_01JGN
|
|
355
|
+
|
|
356
|
+
# Ambiguous prefix (fails with error listing matches)
|
|
357
|
+
opencode-manager sessions delete --session sess_01
|
|
358
|
+
# Error: Multiple sessions match prefix 'sess_01': sess_01JGN..., sess_01ABC...
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
**Commands supporting prefix matching:**
|
|
362
|
+
- `projects delete --id`
|
|
363
|
+
- `sessions delete --session`, `sessions rename --session`, `sessions move --session`, `sessions copy --session`
|
|
364
|
+
- `sessions move --to`, `sessions copy --to` (project ID)
|
|
365
|
+
- `chat list --session`, `chat show --session`
|
|
366
|
+
- `chat show --message` (also supports 1-based `--index`)
|
|
367
|
+
|
|
368
|
+
**Commands requiring exact IDs:**
|
|
369
|
+
- `tokens session --session` — requires full session ID
|
|
370
|
+
- `tokens project --project` — requires full project ID
|
|
371
|
+
|
|
372
|
+
#### Clipboard Support
|
|
373
|
+
|
|
374
|
+
The `--clipboard` flag (or `Y` key in the TUI) copies content to the system clipboard. Platform support:
|
|
375
|
+
|
|
376
|
+
| Platform | Tool Required | Notes |
|
|
377
|
+
|----------|---------------|-------|
|
|
378
|
+
| **macOS** | None | Uses built-in `pbcopy` |
|
|
379
|
+
| **Linux** | `xclip` | Install via `apt install xclip` or equivalent |
|
|
380
|
+
| **Windows** | — | Not currently supported |
|
|
381
|
+
|
|
382
|
+
On Linux, if `xclip` is not installed, clipboard operations will fail silently in the TUI or show an error message in the CLI.
|
|
383
|
+
|
|
384
|
+
#### Delete Semantics
|
|
385
|
+
|
|
386
|
+
Delete commands (`projects delete`, `sessions delete`) remove **metadata files only**:
|
|
387
|
+
|
|
388
|
+
| Command | Deletes | Preserves |
|
|
389
|
+
|---------|---------|-----------|
|
|
390
|
+
| `projects delete` | Project metadata (`storage/project/<id>.json`) | Sessions, messages, parts |
|
|
391
|
+
| `sessions delete` | Session metadata (`storage/sessions/<projectId>.json` entry) | Message and part files |
|
|
392
|
+
|
|
393
|
+
**Safety features:**
|
|
394
|
+
|
|
395
|
+
- **Confirmation required** — Destructive operations require `--yes` flag or interactive confirmation:
|
|
396
|
+
```bash
|
|
397
|
+
# Will prompt for confirmation (interactive)
|
|
398
|
+
opencode-manager projects delete --id prj_abc123
|
|
399
|
+
|
|
400
|
+
# Skip confirmation (scripts)
|
|
401
|
+
opencode-manager projects delete --id prj_abc123 --yes
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
- **Dry-run preview** — Use `--dry-run` to see what would be deleted without making changes:
|
|
405
|
+
```bash
|
|
406
|
+
opencode-manager sessions delete --session sess_xyz789 --dry-run
|
|
407
|
+
# Output shows files that would be affected
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
- **Backup before delete** — Use `--backup-dir` to copy files before deletion:
|
|
411
|
+
```bash
|
|
412
|
+
opencode-manager projects delete --id prj_abc123 --backup-dir ./backups --yes
|
|
413
|
+
# Creates backup, then deletes original
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
**Note:** Session deletion leaves associated chat message files intact. To fully remove a session's data, you would need to manually delete the message files from `storage/message/<sessionId>/`.
|
|
91
417
|
|
|
92
418
|
## Development Workflow
|
|
93
419
|
1. Install dependencies with `bun install`.
|
|
@@ -99,7 +425,9 @@ Keyboard reference:
|
|
|
99
425
|
```
|
|
100
426
|
src/
|
|
101
427
|
bin/opencode-manager.ts # Bun-native CLI shim exposed as the bin entry
|
|
102
|
-
|
|
428
|
+
tui/
|
|
429
|
+
app.tsx # Main TUI implementation (panels, search, help)
|
|
430
|
+
index.tsx # TUI entrypoint with launchTUI(), parseArgs(), bootstrap()
|
|
103
431
|
manage_opencode_projects.py # Legacy Python launcher for backwards compatibility
|
|
104
432
|
opencode-gen.sh # Spec snapshot helper script
|
|
105
433
|
PROJECT-SUMMARY.md # Extended design notes & roadmap
|
package/bun.lock
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
"dependencies": {
|
|
8
8
|
"@opentui/core": "^0.1.44",
|
|
9
9
|
"@opentui/react": "^0.1.44",
|
|
10
|
+
"commander": "^12.0.0",
|
|
11
|
+
"fast-fuzzy": "^1.12.0",
|
|
10
12
|
"react": "^19.0.0",
|
|
11
13
|
},
|
|
12
14
|
"devDependencies": {
|
|
@@ -123,6 +125,8 @@
|
|
|
123
125
|
|
|
124
126
|
"bun-webgpu-win32-x64": ["bun-webgpu-win32-x64@0.1.4", "", { "os": "win32", "cpu": "x64" }, "sha512-Z5yAK28xrcm8Wb5k7TZ8FJKpOI/r+aVCRdlHYAqI2SDJFN3nD4mJs900X6kNVmG/xFzb5yOuKVYWGg+6ZXWbyA=="],
|
|
125
127
|
|
|
128
|
+
"commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="],
|
|
129
|
+
|
|
126
130
|
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
|
127
131
|
|
|
128
132
|
"event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="],
|
|
@@ -131,10 +135,14 @@
|
|
|
131
135
|
|
|
132
136
|
"exif-parser": ["exif-parser@0.1.12", "", {}, "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw=="],
|
|
133
137
|
|
|
138
|
+
"fast-fuzzy": ["fast-fuzzy@1.12.0", "", { "dependencies": { "graphemesplit": "^2.4.1" } }, "sha512-sXxGgHS+ubYpsdLnvOvJ9w5GYYZrtL9mkosG3nfuD446ahvoWEsSKBP7ieGmWIKVLnaxRDgUJkZMdxRgA2Ni+Q=="],
|
|
139
|
+
|
|
134
140
|
"file-type": ["file-type@16.5.4", "", { "dependencies": { "readable-web-to-node-stream": "^3.0.0", "strtok3": "^6.2.4", "token-types": "^4.1.1" } }, "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw=="],
|
|
135
141
|
|
|
136
142
|
"gifwrap": ["gifwrap@0.10.1", "", { "dependencies": { "image-q": "^4.0.0", "omggif": "^1.0.10" } }, "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw=="],
|
|
137
143
|
|
|
144
|
+
"graphemesplit": ["graphemesplit@2.6.0", "", { "dependencies": { "js-base64": "^3.6.0", "unicode-trie": "^2.0.0" } }, "sha512-rG9w2wAfkpg0DILa1pjnjNfucng3usON360shisqIMUBw/87pojcBSrHmeE4UwryAuBih7g8m1oilf5/u8EWdQ=="],
|
|
145
|
+
|
|
138
146
|
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
|
139
147
|
|
|
140
148
|
"image-q": ["image-q@4.0.0", "", { "dependencies": { "@types/node": "16.9.1" } }, "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw=="],
|
|
@@ -143,11 +151,13 @@
|
|
|
143
151
|
|
|
144
152
|
"jpeg-js": ["jpeg-js@0.4.4", "", {}, "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg=="],
|
|
145
153
|
|
|
154
|
+
"js-base64": ["js-base64@3.7.8", "", {}, "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow=="],
|
|
155
|
+
|
|
146
156
|
"mime": ["mime@3.0.0", "", { "bin": "cli.js" }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="],
|
|
147
157
|
|
|
148
158
|
"omggif": ["omggif@1.0.10", "", {}, "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw=="],
|
|
149
159
|
|
|
150
|
-
"pako": ["pako@
|
|
160
|
+
"pako": ["pako@0.2.9", "", {}, "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA=="],
|
|
151
161
|
|
|
152
162
|
"parse-bmfont-ascii": ["parse-bmfont-ascii@1.0.6", "", {}, "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA=="],
|
|
153
163
|
|
|
@@ -189,6 +199,8 @@
|
|
|
189
199
|
|
|
190
200
|
"three": ["three@0.177.0", "", {}, "sha512-EiXv5/qWAaGI+Vz2A+JfavwYCMdGjxVsrn3oBwllUoqYeaBO75J63ZfyaQKoiLrqNHoTlUc6PFgMXnS0kI45zg=="],
|
|
191
201
|
|
|
202
|
+
"tiny-inflate": ["tiny-inflate@1.0.3", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="],
|
|
203
|
+
|
|
192
204
|
"tinycolor2": ["tinycolor2@1.6.0", "", {}, "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw=="],
|
|
193
205
|
|
|
194
206
|
"token-types": ["token-types@4.2.1", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ=="],
|
|
@@ -197,6 +209,8 @@
|
|
|
197
209
|
|
|
198
210
|
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
|
199
211
|
|
|
212
|
+
"unicode-trie": ["unicode-trie@2.0.0", "", { "dependencies": { "pako": "^0.2.5", "tiny-inflate": "^1.0.0" } }, "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ=="],
|
|
213
|
+
|
|
200
214
|
"utif2": ["utif2@4.1.0", "", { "dependencies": { "pako": "^1.0.11" } }, "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w=="],
|
|
201
215
|
|
|
202
216
|
"web-tree-sitter": ["web-tree-sitter@0.25.10", "", { "peerDependencies": { "@types/emscripten": "^1.40.0" }, "optionalPeers": ["@types/emscripten"] }, "sha512-Y09sF44/13XvgVKgO2cNDw5rGk6s26MgoZPXLESvMXeefBf7i6/73eFurre0IsTW6E14Y0ArIzhUMmjoc7xyzA=="],
|
|
@@ -214,5 +228,7 @@
|
|
|
214
228
|
"image-q/@types/node": ["@types/node@16.9.1", "", {}, "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g=="],
|
|
215
229
|
|
|
216
230
|
"pixelmatch/pngjs": ["pngjs@6.0.0", "", {}, "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg=="],
|
|
231
|
+
|
|
232
|
+
"utif2/pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="],
|
|
217
233
|
}
|
|
218
234
|
}
|