opencode-manager 0.3.1 → 0.4.1

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.
@@ -0,0 +1,209 @@
1
+ /**
2
+ * CLI entrypoint module.
3
+ *
4
+ * Provides Commander-based CLI with subcommands for projects, sessions,
5
+ * chat, and tokens. Global options are defined here and passed to
6
+ * subcommand handlers.
7
+ */
8
+
9
+ import { Command, type OptionValues } from "commander"
10
+ import { resolve } from "node:path"
11
+ import { DEFAULT_ROOT } from "../lib/opencode-data"
12
+ import { registerProjectsCommands } from "./commands/projects"
13
+ import { registerSessionsCommands } from "./commands/sessions"
14
+ import { registerChatCommands } from "./commands/chat"
15
+ import { registerTokensCommands } from "./commands/tokens"
16
+ import { registerTUICommand } from "./commands/tui"
17
+
18
+ /**
19
+ * Collect all options from a command and its ancestors.
20
+ * Commander stores global options on the root program, not on subcommands.
21
+ */
22
+ function collectOptions(cmd: Command): OptionValues {
23
+ const opts: OptionValues = {}
24
+ let current: Command | null = cmd
25
+ while (current) {
26
+ Object.assign(opts, current.opts())
27
+ current = current.parent
28
+ }
29
+ return opts
30
+ }
31
+
32
+ /**
33
+ * Global CLI options available to all subcommands.
34
+ */
35
+ export interface GlobalOptions {
36
+ /** Root path to OpenCode metadata store */
37
+ root: string
38
+ /** Output format: json, ndjson, or table */
39
+ format: "json" | "ndjson" | "table"
40
+ /** Maximum number of records to return */
41
+ limit: number
42
+ /** Sort order for list commands */
43
+ sort: "updated" | "created"
44
+ /** Skip confirmation prompts for destructive operations */
45
+ yes: boolean
46
+ /** Show planned changes without executing */
47
+ dryRun: boolean
48
+ /** Suppress non-essential output */
49
+ quiet: boolean
50
+ /** Copy output to clipboard */
51
+ clipboard: boolean
52
+ /** Directory for backup copies before deletion */
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
62
+ }
63
+
64
+ /**
65
+ * Default global options.
66
+ */
67
+ export const DEFAULT_OPTIONS: GlobalOptions = {
68
+ root: DEFAULT_ROOT,
69
+ format: "table",
70
+ limit: 200,
71
+ sort: "updated",
72
+ yes: false,
73
+ dryRun: false,
74
+ quiet: false,
75
+ clipboard: false,
76
+ backupDir: undefined,
77
+ experimentalSqlite: false,
78
+ dbPath: undefined,
79
+ sqliteStrict: false,
80
+ forceWrite: false,
81
+ }
82
+
83
+ /**
84
+ * Create the Commander program with global options and subcommands.
85
+ */
86
+ function createProgram(): Command {
87
+ const program = new Command()
88
+ program.configureHelp({ showGlobalOptions: true })
89
+
90
+ program
91
+ .name("opencode-manager")
92
+ .description("CLI for managing OpenCode metadata stores")
93
+ .version("0.4.1")
94
+ // Global options
95
+ .option(
96
+ "-r, --root <path>",
97
+ "Root path to OpenCode metadata store",
98
+ DEFAULT_OPTIONS.root
99
+ )
100
+ .option(
101
+ "-f, --format <format>",
102
+ "Output format: json, ndjson, or table",
103
+ DEFAULT_OPTIONS.format
104
+ )
105
+ .option(
106
+ "-l, --limit <number>",
107
+ "Maximum number of records to return",
108
+ String(DEFAULT_OPTIONS.limit)
109
+ )
110
+ .option(
111
+ "--sort <order>",
112
+ "Sort order: updated or created",
113
+ DEFAULT_OPTIONS.sort
114
+ )
115
+ .option("-y, --yes", "Skip confirmation prompts", DEFAULT_OPTIONS.yes)
116
+ .option(
117
+ "-n, --dry-run",
118
+ "Show planned changes without executing",
119
+ DEFAULT_OPTIONS.dryRun
120
+ )
121
+ .option("-q, --quiet", "Suppress non-essential output", DEFAULT_OPTIONS.quiet)
122
+ .option("-c, --clipboard", "Copy output to clipboard", DEFAULT_OPTIONS.clipboard)
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
+ )
143
+
144
+ // Projects subcommand group
145
+ registerProjectsCommands(program)
146
+
147
+ // Sessions subcommand group
148
+ registerSessionsCommands(program)
149
+
150
+ // Chat subcommand group
151
+ registerChatCommands(program)
152
+
153
+ // Tokens subcommand group
154
+ registerTokensCommands(program)
155
+
156
+ // TUI subcommand to explicitly launch TUI from CLI
157
+ registerTUICommand(program)
158
+
159
+ return program
160
+ }
161
+
162
+ /**
163
+ * Parse global options from Commander's parsed options object.
164
+ * Resolves paths and converts types as needed.
165
+ */
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
+
171
+ return {
172
+ root: resolve(String(opts.root ?? DEFAULT_OPTIONS.root)),
173
+ format: validateFormat(String(opts.format ?? DEFAULT_OPTIONS.format)),
174
+ limit: parseInt(String(opts.limit ?? DEFAULT_OPTIONS.limit), 10),
175
+ sort: validateSort(String(opts.sort ?? DEFAULT_OPTIONS.sort)),
176
+ yes: Boolean(opts.yes ?? DEFAULT_OPTIONS.yes),
177
+ dryRun: Boolean(opts.dryRun ?? DEFAULT_OPTIONS.dryRun),
178
+ quiet: Boolean(opts.quiet ?? DEFAULT_OPTIONS.quiet),
179
+ clipboard: Boolean(opts.clipboard ?? DEFAULT_OPTIONS.clipboard),
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),
185
+ }
186
+ }
187
+
188
+ function validateFormat(format: string): GlobalOptions["format"] {
189
+ if (format === "json" || format === "ndjson" || format === "table") {
190
+ return format
191
+ }
192
+ return DEFAULT_OPTIONS.format
193
+ }
194
+
195
+ function validateSort(sort: string): GlobalOptions["sort"] {
196
+ if (sort === "updated" || sort === "created") {
197
+ return sort
198
+ }
199
+ return DEFAULT_OPTIONS.sort
200
+ }
201
+
202
+ /**
203
+ * Run the CLI with the given arguments.
204
+ * This is the main entry point called from opencode-manager.ts.
205
+ */
206
+ export async function runCLI(args: string[]): Promise<void> {
207
+ const program = createProgram()
208
+ await program.parseAsync(args, { from: "user" })
209
+ }