opencode-manager 0.4.0 → 0.4.2
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/README.md +70 -9
- package/bun.lock +5 -0
- package/package.json +2 -1
- package/src/cli/commands/chat.ts +37 -23
- package/src/cli/commands/projects.ts +25 -9
- package/src/cli/commands/sessions.ts +52 -27
- package/src/cli/commands/tokens.ts +28 -16
- package/src/cli/index.ts +41 -1
- package/src/cli/resolvers.ts +34 -9
- package/src/lib/opencode-data-provider.ts +685 -0
- package/src/lib/opencode-data-sqlite.ts +1973 -0
- package/tsconfig.json +1 -1
|
@@ -7,13 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { Command, type OptionValues } from "commander"
|
|
9
9
|
import { parseGlobalOptions, type GlobalOptions } from "../index"
|
|
10
|
-
import {
|
|
11
|
-
computeGlobalTokenSummary,
|
|
12
|
-
computeProjectTokenSummary,
|
|
13
|
-
computeSessionTokenSummary,
|
|
14
|
-
loadProjectRecords,
|
|
15
|
-
loadSessionRecords,
|
|
16
|
-
} from "../../lib/opencode-data"
|
|
10
|
+
import { createProviderFromGlobalOptions } from "../../lib/opencode-data-provider"
|
|
17
11
|
import { getOutputOptions, printAggregateTokensOutput, printTokensOutput } from "../output"
|
|
18
12
|
import { handleError } from "../errors"
|
|
19
13
|
import { findProjectById, findSessionById } from "../resolvers"
|
|
@@ -101,6 +95,16 @@ export function registerTokensCommands(parent: Command): void {
|
|
|
101
95
|
handleError(error, globalOpts.format)
|
|
102
96
|
}
|
|
103
97
|
})
|
|
98
|
+
|
|
99
|
+
tokens.addHelpText(
|
|
100
|
+
"after",
|
|
101
|
+
[
|
|
102
|
+
"",
|
|
103
|
+
"Examples:",
|
|
104
|
+
" opencode-manager tokens session --session <id> --experimental-sqlite",
|
|
105
|
+
" opencode-manager tokens global --db ~/.local/share/opencode/opencode.db",
|
|
106
|
+
].join("\n")
|
|
107
|
+
)
|
|
104
108
|
}
|
|
105
109
|
|
|
106
110
|
/**
|
|
@@ -110,14 +114,17 @@ async function handleTokensSession(
|
|
|
110
114
|
globalOpts: GlobalOptions,
|
|
111
115
|
sessionOpts: TokensSessionOptions
|
|
112
116
|
): Promise<void> {
|
|
117
|
+
// Create provider based on global options (JSONL or SQLite)
|
|
118
|
+
const provider = createProviderFromGlobalOptions(globalOpts)
|
|
119
|
+
|
|
113
120
|
// Load all sessions to find the one we want
|
|
114
|
-
const sessions = await loadSessionRecords(
|
|
121
|
+
const sessions = await provider.loadSessionRecords()
|
|
115
122
|
|
|
116
123
|
// Find the session by ID
|
|
117
124
|
const session = findSessionById(sessions, sessionOpts.session)
|
|
118
125
|
|
|
119
126
|
// Compute token summary for the session
|
|
120
|
-
const summary = await computeSessionTokenSummary(session
|
|
127
|
+
const summary = await provider.computeSessionTokenSummary(session)
|
|
121
128
|
|
|
122
129
|
// Output the result
|
|
123
130
|
const outputOpts = getOutputOptions(globalOpts)
|
|
@@ -131,20 +138,22 @@ async function handleTokensProject(
|
|
|
131
138
|
globalOpts: GlobalOptions,
|
|
132
139
|
projectOpts: TokensProjectOptions
|
|
133
140
|
): Promise<void> {
|
|
141
|
+
// Create provider based on global options (JSONL or SQLite)
|
|
142
|
+
const provider = createProviderFromGlobalOptions(globalOpts)
|
|
143
|
+
|
|
134
144
|
// Load all projects to validate the project exists
|
|
135
|
-
const projects = await loadProjectRecords(
|
|
145
|
+
const projects = await provider.loadProjectRecords()
|
|
136
146
|
|
|
137
147
|
// Find the project by ID (throws if not found)
|
|
138
148
|
findProjectById(projects, projectOpts.project)
|
|
139
149
|
|
|
140
150
|
// Load all sessions to compute token summary
|
|
141
|
-
const sessions = await loadSessionRecords(
|
|
151
|
+
const sessions = await provider.loadSessionRecords()
|
|
142
152
|
|
|
143
153
|
// Compute token summary for the project
|
|
144
|
-
const summary = await computeProjectTokenSummary(
|
|
154
|
+
const summary = await provider.computeProjectTokenSummary(
|
|
145
155
|
projectOpts.project,
|
|
146
|
-
sessions
|
|
147
|
-
globalOpts.root
|
|
156
|
+
sessions
|
|
148
157
|
)
|
|
149
158
|
|
|
150
159
|
// Output the result
|
|
@@ -156,11 +165,14 @@ async function handleTokensProject(
|
|
|
156
165
|
* Handle the tokens global command.
|
|
157
166
|
*/
|
|
158
167
|
async function handleTokensGlobal(globalOpts: GlobalOptions): Promise<void> {
|
|
168
|
+
// Create provider based on global options (JSONL or SQLite)
|
|
169
|
+
const provider = createProviderFromGlobalOptions(globalOpts)
|
|
170
|
+
|
|
159
171
|
// Load all sessions to compute global token summary
|
|
160
|
-
const sessions = await loadSessionRecords(
|
|
172
|
+
const sessions = await provider.loadSessionRecords()
|
|
161
173
|
|
|
162
174
|
// Compute token summary across all sessions
|
|
163
|
-
const summary = await computeGlobalTokenSummary(sessions
|
|
175
|
+
const summary = await provider.computeGlobalTokenSummary(sessions)
|
|
164
176
|
|
|
165
177
|
// Output the result
|
|
166
178
|
const outputOpts = getOutputOptions(globalOpts)
|
package/src/cli/index.ts
CHANGED
|
@@ -51,6 +51,14 @@ export interface GlobalOptions {
|
|
|
51
51
|
clipboard: boolean
|
|
52
52
|
/** Directory for backup copies before deletion */
|
|
53
53
|
backupDir?: string
|
|
54
|
+
/** Use SQLite database instead of JSONL files (experimental) */
|
|
55
|
+
experimentalSqlite: boolean
|
|
56
|
+
/** Path to SQLite database (implies --experimental-sqlite) */
|
|
57
|
+
dbPath?: string
|
|
58
|
+
/** Fail fast on any SQLite error or malformed data */
|
|
59
|
+
sqliteStrict: boolean
|
|
60
|
+
/** Wait for SQLite write locks to clear before failing */
|
|
61
|
+
forceWrite: boolean
|
|
54
62
|
}
|
|
55
63
|
|
|
56
64
|
/**
|
|
@@ -66,6 +74,10 @@ export const DEFAULT_OPTIONS: GlobalOptions = {
|
|
|
66
74
|
quiet: false,
|
|
67
75
|
clipboard: false,
|
|
68
76
|
backupDir: undefined,
|
|
77
|
+
experimentalSqlite: false,
|
|
78
|
+
dbPath: undefined,
|
|
79
|
+
sqliteStrict: false,
|
|
80
|
+
forceWrite: false,
|
|
69
81
|
}
|
|
70
82
|
|
|
71
83
|
/**
|
|
@@ -73,11 +85,12 @@ export const DEFAULT_OPTIONS: GlobalOptions = {
|
|
|
73
85
|
*/
|
|
74
86
|
function createProgram(): Command {
|
|
75
87
|
const program = new Command()
|
|
88
|
+
program.configureHelp({ showGlobalOptions: true })
|
|
76
89
|
|
|
77
90
|
program
|
|
78
91
|
.name("opencode-manager")
|
|
79
92
|
.description("CLI for managing OpenCode metadata stores")
|
|
80
|
-
.version("0.4.
|
|
93
|
+
.version("0.4.2")
|
|
81
94
|
// Global options
|
|
82
95
|
.option(
|
|
83
96
|
"-r, --root <path>",
|
|
@@ -108,6 +121,25 @@ function createProgram(): Command {
|
|
|
108
121
|
.option("-q, --quiet", "Suppress non-essential output", DEFAULT_OPTIONS.quiet)
|
|
109
122
|
.option("-c, --clipboard", "Copy output to clipboard", DEFAULT_OPTIONS.clipboard)
|
|
110
123
|
.option("--backup-dir <path>", "Directory for backup copies before deletion")
|
|
124
|
+
.option(
|
|
125
|
+
"--experimental-sqlite",
|
|
126
|
+
"Use SQLite database instead of JSONL files (experimental; schema may change)",
|
|
127
|
+
DEFAULT_OPTIONS.experimentalSqlite
|
|
128
|
+
)
|
|
129
|
+
.option(
|
|
130
|
+
"--db <path>",
|
|
131
|
+
"Path to SQLite database (implies --experimental-sqlite). Default: ~/.local/share/opencode/opencode.db"
|
|
132
|
+
)
|
|
133
|
+
.option(
|
|
134
|
+
"--sqlite-strict",
|
|
135
|
+
"Fail on any SQLite warning or malformed data (no partial results)",
|
|
136
|
+
DEFAULT_OPTIONS.sqliteStrict
|
|
137
|
+
)
|
|
138
|
+
.option(
|
|
139
|
+
"--force-write",
|
|
140
|
+
"Wait for SQLite write locks to clear before failing",
|
|
141
|
+
DEFAULT_OPTIONS.forceWrite
|
|
142
|
+
)
|
|
111
143
|
|
|
112
144
|
// Projects subcommand group
|
|
113
145
|
registerProjectsCommands(program)
|
|
@@ -132,6 +164,10 @@ function createProgram(): Command {
|
|
|
132
164
|
* Resolves paths and converts types as needed.
|
|
133
165
|
*/
|
|
134
166
|
export function parseGlobalOptions(opts: Record<string, unknown>): GlobalOptions {
|
|
167
|
+
// --db implies --experimental-sqlite
|
|
168
|
+
const dbPath = opts.db ? resolve(String(opts.db)) : undefined
|
|
169
|
+
const experimentalSqlite = Boolean(opts.experimentalSqlite) || dbPath !== undefined
|
|
170
|
+
|
|
135
171
|
return {
|
|
136
172
|
root: resolve(String(opts.root ?? DEFAULT_OPTIONS.root)),
|
|
137
173
|
format: validateFormat(String(opts.format ?? DEFAULT_OPTIONS.format)),
|
|
@@ -142,6 +178,10 @@ export function parseGlobalOptions(opts: Record<string, unknown>): GlobalOptions
|
|
|
142
178
|
quiet: Boolean(opts.quiet ?? DEFAULT_OPTIONS.quiet),
|
|
143
179
|
clipboard: Boolean(opts.clipboard ?? DEFAULT_OPTIONS.clipboard),
|
|
144
180
|
backupDir: opts.backupDir ? resolve(String(opts.backupDir)) : undefined,
|
|
181
|
+
experimentalSqlite,
|
|
182
|
+
dbPath,
|
|
183
|
+
sqliteStrict: Boolean(opts.sqliteStrict ?? DEFAULT_OPTIONS.sqliteStrict),
|
|
184
|
+
forceWrite: Boolean(opts.forceWrite ?? DEFAULT_OPTIONS.forceWrite),
|
|
145
185
|
}
|
|
146
186
|
}
|
|
147
187
|
|
package/src/cli/resolvers.ts
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* These helpers provide consistent ID resolution across all CLI commands,
|
|
5
5
|
* supporting both exact matches and flexible matching patterns.
|
|
6
|
+
*
|
|
7
|
+
* Resolvers can optionally accept a DataProvider to support both JSONL and
|
|
8
|
+
* SQLite backends. When no provider is given, they fall back to direct JSONL
|
|
9
|
+
* loading for backward compatibility.
|
|
6
10
|
*/
|
|
7
11
|
|
|
8
12
|
import {
|
|
@@ -13,6 +17,7 @@ import {
|
|
|
13
17
|
type ProjectRecord,
|
|
14
18
|
type SessionRecord,
|
|
15
19
|
} from "../lib/opencode-data"
|
|
20
|
+
import { type DataProvider } from "../lib/opencode-data-provider"
|
|
16
21
|
import { NotFoundError, projectNotFound, sessionNotFound } from "./errors"
|
|
17
22
|
|
|
18
23
|
// ========================
|
|
@@ -29,6 +34,13 @@ export interface ResolveSessionOptions extends SessionLoadOptions {
|
|
|
29
34
|
* Defaults to false.
|
|
30
35
|
*/
|
|
31
36
|
allowPrefix?: boolean
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Optional data provider for backend-agnostic data loading.
|
|
40
|
+
* When provided, uses the provider's loadSessionRecords method.
|
|
41
|
+
* When omitted, falls back to direct JSONL loading for backward compatibility.
|
|
42
|
+
*/
|
|
43
|
+
provider?: DataProvider
|
|
32
44
|
}
|
|
33
45
|
|
|
34
46
|
/**
|
|
@@ -81,7 +93,7 @@ export function findSessionsByPrefix(
|
|
|
81
93
|
* Supports exact matching and optional prefix matching.
|
|
82
94
|
*
|
|
83
95
|
* @param sessionId - Session ID or prefix to resolve
|
|
84
|
-
* @param options - Resolution options including root and
|
|
96
|
+
* @param options - Resolution options including root, projectId filters, and optional provider
|
|
85
97
|
* @returns Resolution result with session and metadata
|
|
86
98
|
* @throws NotFoundError if no session matches
|
|
87
99
|
* @throws NotFoundError if prefix matches multiple sessions (ambiguous)
|
|
@@ -90,10 +102,13 @@ export async function resolveSessionId(
|
|
|
90
102
|
sessionId: string,
|
|
91
103
|
options: ResolveSessionOptions = {}
|
|
92
104
|
): Promise<ResolveSessionResult> {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
projectId: options.projectId
|
|
96
|
-
|
|
105
|
+
// Use provider if available, otherwise fall back to direct JSONL loading
|
|
106
|
+
const sessions = options.provider
|
|
107
|
+
? await options.provider.loadSessionRecords({ projectId: options.projectId })
|
|
108
|
+
: await loadSessionRecords({
|
|
109
|
+
root: options.root,
|
|
110
|
+
projectId: options.projectId,
|
|
111
|
+
})
|
|
97
112
|
|
|
98
113
|
// Try exact match first
|
|
99
114
|
const exactMatch = sessions.find((s) => s.sessionId === sessionId)
|
|
@@ -146,6 +161,13 @@ export interface ResolveProjectOptions extends LoadOptions {
|
|
|
146
161
|
* Defaults to false.
|
|
147
162
|
*/
|
|
148
163
|
allowPrefix?: boolean
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Optional data provider for backend-agnostic data loading.
|
|
167
|
+
* When provided, uses the provider's loadProjectRecords method.
|
|
168
|
+
* When omitted, falls back to direct JSONL loading for backward compatibility.
|
|
169
|
+
*/
|
|
170
|
+
provider?: DataProvider
|
|
149
171
|
}
|
|
150
172
|
|
|
151
173
|
/**
|
|
@@ -198,7 +220,7 @@ export function findProjectsByPrefix(
|
|
|
198
220
|
* Supports exact matching and optional prefix matching.
|
|
199
221
|
*
|
|
200
222
|
* @param projectId - Project ID or prefix to resolve
|
|
201
|
-
* @param options - Resolution options including root
|
|
223
|
+
* @param options - Resolution options including root and optional provider
|
|
202
224
|
* @returns Resolution result with project and metadata
|
|
203
225
|
* @throws NotFoundError if no project matches
|
|
204
226
|
* @throws NotFoundError if prefix matches multiple projects (ambiguous)
|
|
@@ -207,9 +229,12 @@ export async function resolveProjectId(
|
|
|
207
229
|
projectId: string,
|
|
208
230
|
options: ResolveProjectOptions = {}
|
|
209
231
|
): Promise<ResolveProjectResult> {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
232
|
+
// Use provider if available, otherwise fall back to direct JSONL loading
|
|
233
|
+
const projects = options.provider
|
|
234
|
+
? await options.provider.loadProjectRecords()
|
|
235
|
+
: await loadProjectRecords({
|
|
236
|
+
root: options.root,
|
|
237
|
+
})
|
|
213
238
|
|
|
214
239
|
// Try exact match first
|
|
215
240
|
const exactMatch = projects.find((p) => p.projectId === projectId)
|