opencode-manager 0.3.1 → 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
|
@@ -0,0 +1,661 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI output module.
|
|
3
|
+
*
|
|
4
|
+
* Provides a unified interface for outputting data in different formats
|
|
5
|
+
* based on the --format global option (json, ndjson, table).
|
|
6
|
+
*
|
|
7
|
+
* This module acts as a router that selects the appropriate formatter
|
|
8
|
+
* based on the format option and handles all domain-specific output types.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { GlobalOptions } from "./index"
|
|
12
|
+
import type {
|
|
13
|
+
AggregateTokenSummary,
|
|
14
|
+
ChatMessage,
|
|
15
|
+
ChatSearchResult,
|
|
16
|
+
ProjectRecord,
|
|
17
|
+
SessionRecord,
|
|
18
|
+
TokenSummary,
|
|
19
|
+
} from "../lib/opencode-data"
|
|
20
|
+
|
|
21
|
+
// Import formatters
|
|
22
|
+
import {
|
|
23
|
+
formatJson,
|
|
24
|
+
formatJsonArraySuccess,
|
|
25
|
+
formatJsonError,
|
|
26
|
+
formatJsonSuccess,
|
|
27
|
+
printJson,
|
|
28
|
+
printJsonArraySuccess,
|
|
29
|
+
printJsonError,
|
|
30
|
+
printJsonSuccess,
|
|
31
|
+
type JsonFormatOptions,
|
|
32
|
+
type JsonResponse,
|
|
33
|
+
} from "./formatters/json"
|
|
34
|
+
import { formatNdjson, printNdjson } from "./formatters/ndjson"
|
|
35
|
+
import {
|
|
36
|
+
formatAggregateTokenSummary,
|
|
37
|
+
formatChatSearchTable,
|
|
38
|
+
formatChatTable,
|
|
39
|
+
formatProjectsTable,
|
|
40
|
+
formatSessionsTable,
|
|
41
|
+
formatTokenSummary,
|
|
42
|
+
printAggregateTokenSummary,
|
|
43
|
+
printChatSearchTable,
|
|
44
|
+
printChatTable,
|
|
45
|
+
printProjectsTable,
|
|
46
|
+
printSessionsTable,
|
|
47
|
+
printTokenSummary,
|
|
48
|
+
type IndexedChatSearchResult,
|
|
49
|
+
type TableFormatOptions,
|
|
50
|
+
} from "./formatters/table"
|
|
51
|
+
|
|
52
|
+
// ========================
|
|
53
|
+
// Types
|
|
54
|
+
// ========================
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Output format types supported by the CLI.
|
|
58
|
+
*/
|
|
59
|
+
export type OutputFormat = "json" | "ndjson" | "table"
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Output options derived from global CLI options.
|
|
63
|
+
*/
|
|
64
|
+
export interface OutputOptions {
|
|
65
|
+
format: OutputFormat
|
|
66
|
+
quiet?: boolean
|
|
67
|
+
/** Metadata for list responses (limit, truncation info) */
|
|
68
|
+
meta?: {
|
|
69
|
+
limit?: number
|
|
70
|
+
truncated?: boolean
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Extract output options from global CLI options.
|
|
76
|
+
*/
|
|
77
|
+
export function getOutputOptions(globalOpts: GlobalOptions): OutputOptions {
|
|
78
|
+
return {
|
|
79
|
+
format: globalOpts.format,
|
|
80
|
+
quiet: globalOpts.quiet,
|
|
81
|
+
meta: {
|
|
82
|
+
limit: globalOpts.limit,
|
|
83
|
+
},
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ========================
|
|
88
|
+
// Generic Output Functions
|
|
89
|
+
// ========================
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Format any data array using the specified output format.
|
|
93
|
+
* This is the low-level generic formatter - prefer domain-specific functions.
|
|
94
|
+
*/
|
|
95
|
+
export function formatOutput<T>(
|
|
96
|
+
data: T[],
|
|
97
|
+
format: OutputFormat,
|
|
98
|
+
tableFormatter?: (data: T[], options?: TableFormatOptions) => string,
|
|
99
|
+
options?: OutputOptions
|
|
100
|
+
): string {
|
|
101
|
+
switch (format) {
|
|
102
|
+
case "json":
|
|
103
|
+
return formatJsonArraySuccess(data, options?.meta, {
|
|
104
|
+
pretty: process.stdout.isTTY,
|
|
105
|
+
})
|
|
106
|
+
case "ndjson":
|
|
107
|
+
return formatNdjson(data)
|
|
108
|
+
case "table":
|
|
109
|
+
if (tableFormatter) {
|
|
110
|
+
return tableFormatter(data)
|
|
111
|
+
}
|
|
112
|
+
// Fallback: format as JSON if no table formatter provided
|
|
113
|
+
return formatJsonArraySuccess(data, options?.meta)
|
|
114
|
+
default:
|
|
115
|
+
// Exhaustive check
|
|
116
|
+
const _exhaustive: never = format
|
|
117
|
+
throw new Error(`Unknown format: ${_exhaustive}`)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Print any data array using the specified output format.
|
|
123
|
+
*/
|
|
124
|
+
export function printOutput<T>(
|
|
125
|
+
data: T[],
|
|
126
|
+
format: OutputFormat,
|
|
127
|
+
tableFormatter?: (data: T[], options?: TableFormatOptions) => string,
|
|
128
|
+
options?: OutputOptions
|
|
129
|
+
): void {
|
|
130
|
+
console.log(formatOutput(data, format, tableFormatter, options))
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Format a single item using the specified output format.
|
|
135
|
+
*/
|
|
136
|
+
export function formatSingleOutput<T>(
|
|
137
|
+
data: T,
|
|
138
|
+
format: OutputFormat,
|
|
139
|
+
tableFormatter?: (data: T, options?: TableFormatOptions) => string
|
|
140
|
+
): string {
|
|
141
|
+
switch (format) {
|
|
142
|
+
case "json":
|
|
143
|
+
return formatJsonSuccess(data, undefined, {
|
|
144
|
+
pretty: process.stdout.isTTY,
|
|
145
|
+
})
|
|
146
|
+
case "ndjson":
|
|
147
|
+
return formatNdjson([data])
|
|
148
|
+
case "table":
|
|
149
|
+
if (tableFormatter) {
|
|
150
|
+
return tableFormatter(data)
|
|
151
|
+
}
|
|
152
|
+
return formatJsonSuccess(data)
|
|
153
|
+
default:
|
|
154
|
+
const _exhaustive: never = format
|
|
155
|
+
throw new Error(`Unknown format: ${_exhaustive}`)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Print a single item using the specified output format.
|
|
161
|
+
*/
|
|
162
|
+
export function printSingleOutput<T>(
|
|
163
|
+
data: T,
|
|
164
|
+
format: OutputFormat,
|
|
165
|
+
tableFormatter?: (data: T, options?: TableFormatOptions) => string
|
|
166
|
+
): void {
|
|
167
|
+
console.log(formatSingleOutput(data, format, tableFormatter))
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ========================
|
|
171
|
+
// Projects Output
|
|
172
|
+
// ========================
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Format projects list for output.
|
|
176
|
+
*/
|
|
177
|
+
export function formatProjectsOutput(
|
|
178
|
+
projects: ProjectRecord[],
|
|
179
|
+
options: OutputOptions
|
|
180
|
+
): string {
|
|
181
|
+
switch (options.format) {
|
|
182
|
+
case "json":
|
|
183
|
+
return formatJsonArraySuccess(projects, options.meta, {
|
|
184
|
+
pretty: process.stdout.isTTY,
|
|
185
|
+
})
|
|
186
|
+
case "ndjson":
|
|
187
|
+
return formatNdjson(projects)
|
|
188
|
+
case "table":
|
|
189
|
+
return formatProjectsTable(projects)
|
|
190
|
+
default:
|
|
191
|
+
const _exhaustive: never = options.format
|
|
192
|
+
throw new Error(`Unknown format: ${_exhaustive}`)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Print projects list to stdout.
|
|
198
|
+
*/
|
|
199
|
+
export function printProjectsOutput(
|
|
200
|
+
projects: ProjectRecord[],
|
|
201
|
+
options: OutputOptions
|
|
202
|
+
): void {
|
|
203
|
+
if (options.quiet && options.format === "table") {
|
|
204
|
+
// In quiet mode with table format, just show count
|
|
205
|
+
console.log(`${projects.length} project(s)`)
|
|
206
|
+
return
|
|
207
|
+
}
|
|
208
|
+
console.log(formatProjectsOutput(projects, options))
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ========================
|
|
212
|
+
// Sessions Output
|
|
213
|
+
// ========================
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Format sessions list for output.
|
|
217
|
+
*/
|
|
218
|
+
export function formatSessionsOutput(
|
|
219
|
+
sessions: SessionRecord[],
|
|
220
|
+
options: OutputOptions
|
|
221
|
+
): string {
|
|
222
|
+
switch (options.format) {
|
|
223
|
+
case "json":
|
|
224
|
+
return formatJsonArraySuccess(sessions, options.meta, {
|
|
225
|
+
pretty: process.stdout.isTTY,
|
|
226
|
+
})
|
|
227
|
+
case "ndjson":
|
|
228
|
+
return formatNdjson(sessions)
|
|
229
|
+
case "table":
|
|
230
|
+
return formatSessionsTable(sessions)
|
|
231
|
+
default:
|
|
232
|
+
const _exhaustive: never = options.format
|
|
233
|
+
throw new Error(`Unknown format: ${_exhaustive}`)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Print sessions list to stdout.
|
|
239
|
+
*/
|
|
240
|
+
export function printSessionsOutput(
|
|
241
|
+
sessions: SessionRecord[],
|
|
242
|
+
options: OutputOptions
|
|
243
|
+
): void {
|
|
244
|
+
if (options.quiet && options.format === "table") {
|
|
245
|
+
console.log(`${sessions.length} session(s)`)
|
|
246
|
+
return
|
|
247
|
+
}
|
|
248
|
+
console.log(formatSessionsOutput(sessions, options))
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ========================
|
|
252
|
+
// Chat Output
|
|
253
|
+
// ========================
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Chat message with index for list display.
|
|
257
|
+
*/
|
|
258
|
+
export type IndexedChatMessage = ChatMessage & { index: number }
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Format chat messages list for output.
|
|
262
|
+
*/
|
|
263
|
+
export function formatChatOutput(
|
|
264
|
+
messages: IndexedChatMessage[],
|
|
265
|
+
options: OutputOptions
|
|
266
|
+
): string {
|
|
267
|
+
switch (options.format) {
|
|
268
|
+
case "json":
|
|
269
|
+
return formatJsonArraySuccess(messages, options.meta, {
|
|
270
|
+
pretty: process.stdout.isTTY,
|
|
271
|
+
})
|
|
272
|
+
case "ndjson":
|
|
273
|
+
return formatNdjson(messages)
|
|
274
|
+
case "table":
|
|
275
|
+
return formatChatTable(messages)
|
|
276
|
+
default:
|
|
277
|
+
const _exhaustive: never = options.format
|
|
278
|
+
throw new Error(`Unknown format: ${_exhaustive}`)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Print chat messages list to stdout.
|
|
284
|
+
*/
|
|
285
|
+
export function printChatOutput(
|
|
286
|
+
messages: IndexedChatMessage[],
|
|
287
|
+
options: OutputOptions
|
|
288
|
+
): void {
|
|
289
|
+
if (options.quiet && options.format === "table") {
|
|
290
|
+
console.log(`${messages.length} message(s)`)
|
|
291
|
+
return
|
|
292
|
+
}
|
|
293
|
+
console.log(formatChatOutput(messages, options))
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Format a single chat message for detailed display.
|
|
298
|
+
* For table format, shows the full message content rather than a table row.
|
|
299
|
+
*/
|
|
300
|
+
export function formatChatMessageOutput(
|
|
301
|
+
message: ChatMessage,
|
|
302
|
+
format: OutputFormat
|
|
303
|
+
): string {
|
|
304
|
+
switch (format) {
|
|
305
|
+
case "json":
|
|
306
|
+
return formatJsonSuccess(message, undefined, {
|
|
307
|
+
pretty: process.stdout.isTTY,
|
|
308
|
+
})
|
|
309
|
+
case "ndjson":
|
|
310
|
+
return formatNdjson([message])
|
|
311
|
+
case "table":
|
|
312
|
+
// For single message display, show formatted content
|
|
313
|
+
const lines: string[] = []
|
|
314
|
+
lines.push(`Message ID: ${message.messageId}`)
|
|
315
|
+
lines.push(`Role: ${message.role}`)
|
|
316
|
+
lines.push(`Created: ${message.createdAt?.toISOString() ?? "unknown"}`)
|
|
317
|
+
if (message.tokens) {
|
|
318
|
+
lines.push(`Tokens: ${message.tokens.total}`)
|
|
319
|
+
}
|
|
320
|
+
lines.push("")
|
|
321
|
+
lines.push("Content:")
|
|
322
|
+
lines.push("-".repeat(40))
|
|
323
|
+
lines.push(message.previewText ?? "[No content]")
|
|
324
|
+
return lines.join("\n")
|
|
325
|
+
default:
|
|
326
|
+
const _exhaustive: never = format
|
|
327
|
+
throw new Error(`Unknown format: ${_exhaustive}`)
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Print a single chat message to stdout.
|
|
333
|
+
*/
|
|
334
|
+
export function printChatMessageOutput(
|
|
335
|
+
message: ChatMessage,
|
|
336
|
+
format: OutputFormat
|
|
337
|
+
): void {
|
|
338
|
+
console.log(formatChatMessageOutput(message, format))
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// ========================
|
|
342
|
+
// Chat Search Output
|
|
343
|
+
// ========================
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Format chat search results for output.
|
|
347
|
+
*/
|
|
348
|
+
export function formatChatSearchOutput(
|
|
349
|
+
results: IndexedChatSearchResult[],
|
|
350
|
+
options: OutputOptions
|
|
351
|
+
): string {
|
|
352
|
+
switch (options.format) {
|
|
353
|
+
case "json":
|
|
354
|
+
return formatJsonArraySuccess(results, options.meta, {
|
|
355
|
+
pretty: process.stdout.isTTY,
|
|
356
|
+
})
|
|
357
|
+
case "ndjson":
|
|
358
|
+
return formatNdjson(results)
|
|
359
|
+
case "table":
|
|
360
|
+
return formatChatSearchTable(results)
|
|
361
|
+
default:
|
|
362
|
+
const _exhaustive: never = options.format
|
|
363
|
+
throw new Error(`Unknown format: ${_exhaustive}`)
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Print chat search results to stdout.
|
|
369
|
+
*/
|
|
370
|
+
export function printChatSearchOutput(
|
|
371
|
+
results: IndexedChatSearchResult[],
|
|
372
|
+
options: OutputOptions
|
|
373
|
+
): void {
|
|
374
|
+
if (options.quiet && options.format === "table") {
|
|
375
|
+
console.log(`${results.length} match(es)`)
|
|
376
|
+
return
|
|
377
|
+
}
|
|
378
|
+
console.log(formatChatSearchOutput(results, options))
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// ========================
|
|
382
|
+
// Tokens Output
|
|
383
|
+
// ========================
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Format token summary for output.
|
|
387
|
+
*/
|
|
388
|
+
export function formatTokensOutput(
|
|
389
|
+
summary: TokenSummary,
|
|
390
|
+
format: OutputFormat
|
|
391
|
+
): string {
|
|
392
|
+
switch (format) {
|
|
393
|
+
case "json":
|
|
394
|
+
return formatJsonSuccess(summary, undefined, {
|
|
395
|
+
pretty: process.stdout.isTTY,
|
|
396
|
+
})
|
|
397
|
+
case "ndjson":
|
|
398
|
+
return formatNdjson([summary])
|
|
399
|
+
case "table":
|
|
400
|
+
return formatTokenSummary(summary)
|
|
401
|
+
default:
|
|
402
|
+
const _exhaustive: never = format
|
|
403
|
+
throw new Error(`Unknown format: ${_exhaustive}`)
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Print token summary to stdout.
|
|
409
|
+
*/
|
|
410
|
+
export function printTokensOutput(
|
|
411
|
+
summary: TokenSummary,
|
|
412
|
+
format: OutputFormat
|
|
413
|
+
): void {
|
|
414
|
+
console.log(formatTokensOutput(summary, format))
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Format aggregate token summary for output.
|
|
419
|
+
*/
|
|
420
|
+
export function formatAggregateTokensOutput(
|
|
421
|
+
summary: AggregateTokenSummary,
|
|
422
|
+
format: OutputFormat,
|
|
423
|
+
label?: string
|
|
424
|
+
): string {
|
|
425
|
+
switch (format) {
|
|
426
|
+
case "json":
|
|
427
|
+
return formatJsonSuccess(summary, undefined, {
|
|
428
|
+
pretty: process.stdout.isTTY,
|
|
429
|
+
})
|
|
430
|
+
case "ndjson":
|
|
431
|
+
return formatNdjson([summary])
|
|
432
|
+
case "table":
|
|
433
|
+
return formatAggregateTokenSummary(summary, { label })
|
|
434
|
+
default:
|
|
435
|
+
const _exhaustive: never = format
|
|
436
|
+
throw new Error(`Unknown format: ${_exhaustive}`)
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Print aggregate token summary to stdout.
|
|
442
|
+
*/
|
|
443
|
+
export function printAggregateTokensOutput(
|
|
444
|
+
summary: AggregateTokenSummary,
|
|
445
|
+
format: OutputFormat,
|
|
446
|
+
label?: string
|
|
447
|
+
): void {
|
|
448
|
+
console.log(formatAggregateTokensOutput(summary, format, label))
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// ========================
|
|
452
|
+
// Error Output
|
|
453
|
+
// ========================
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Format an error for output.
|
|
457
|
+
*/
|
|
458
|
+
export function formatErrorOutput(
|
|
459
|
+
error: string | Error,
|
|
460
|
+
format: OutputFormat
|
|
461
|
+
): string {
|
|
462
|
+
switch (format) {
|
|
463
|
+
case "json":
|
|
464
|
+
case "ndjson":
|
|
465
|
+
return formatJsonError(error, { pretty: process.stdout.isTTY })
|
|
466
|
+
case "table":
|
|
467
|
+
// For table format, just return the error message
|
|
468
|
+
const message = error instanceof Error ? error.message : error
|
|
469
|
+
return `Error: ${message}`
|
|
470
|
+
default:
|
|
471
|
+
const _exhaustive: never = format
|
|
472
|
+
throw new Error(`Unknown format: ${_exhaustive}`)
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Print an error to stderr.
|
|
478
|
+
*/
|
|
479
|
+
export function printErrorOutput(
|
|
480
|
+
error: string | Error,
|
|
481
|
+
format: OutputFormat
|
|
482
|
+
): void {
|
|
483
|
+
console.error(formatErrorOutput(error, format))
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// ========================
|
|
487
|
+
// Success/Info Output
|
|
488
|
+
// ========================
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Format a success message for output.
|
|
492
|
+
*/
|
|
493
|
+
export function formatSuccessOutput(
|
|
494
|
+
message: string,
|
|
495
|
+
data?: Record<string, unknown>,
|
|
496
|
+
format: OutputFormat = "table"
|
|
497
|
+
): string {
|
|
498
|
+
switch (format) {
|
|
499
|
+
case "json":
|
|
500
|
+
case "ndjson":
|
|
501
|
+
return formatJsonSuccess(
|
|
502
|
+
data ?? { message },
|
|
503
|
+
undefined,
|
|
504
|
+
{ pretty: process.stdout.isTTY }
|
|
505
|
+
)
|
|
506
|
+
case "table":
|
|
507
|
+
return message
|
|
508
|
+
default:
|
|
509
|
+
const _exhaustive: never = format
|
|
510
|
+
throw new Error(`Unknown format: ${_exhaustive}`)
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Print a success message to stdout.
|
|
516
|
+
*/
|
|
517
|
+
export function printSuccessOutput(
|
|
518
|
+
message: string,
|
|
519
|
+
data?: Record<string, unknown>,
|
|
520
|
+
format: OutputFormat = "table"
|
|
521
|
+
): void {
|
|
522
|
+
console.log(formatSuccessOutput(message, data, format))
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// ========================
|
|
526
|
+
// Dry-Run Output
|
|
527
|
+
// ========================
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Dry-run result for delete operations.
|
|
531
|
+
* Mirrors DeleteResult from opencode-data but typed for CLI output.
|
|
532
|
+
*/
|
|
533
|
+
export interface DryRunResult {
|
|
534
|
+
/** Paths that would be affected */
|
|
535
|
+
paths: string[]
|
|
536
|
+
/** Operation that would be performed */
|
|
537
|
+
operation: "delete" | "backup" | "move" | "copy"
|
|
538
|
+
/** Resource type (project, session) */
|
|
539
|
+
resourceType: "project" | "session"
|
|
540
|
+
/** Count of items affected */
|
|
541
|
+
count: number
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Format dry-run output showing what would be affected.
|
|
546
|
+
*
|
|
547
|
+
* @param result - The dry-run result
|
|
548
|
+
* @param format - Output format
|
|
549
|
+
* @returns Formatted string
|
|
550
|
+
*/
|
|
551
|
+
export function formatDryRunOutput(
|
|
552
|
+
result: DryRunResult,
|
|
553
|
+
format: OutputFormat
|
|
554
|
+
): string {
|
|
555
|
+
switch (format) {
|
|
556
|
+
case "json":
|
|
557
|
+
return formatJsonSuccess(
|
|
558
|
+
{
|
|
559
|
+
dryRun: true,
|
|
560
|
+
operation: result.operation,
|
|
561
|
+
resourceType: result.resourceType,
|
|
562
|
+
count: result.count,
|
|
563
|
+
paths: result.paths,
|
|
564
|
+
},
|
|
565
|
+
undefined,
|
|
566
|
+
{ pretty: process.stdout.isTTY }
|
|
567
|
+
)
|
|
568
|
+
case "ndjson":
|
|
569
|
+
return formatNdjson(
|
|
570
|
+
result.paths.map((path) => ({
|
|
571
|
+
dryRun: true,
|
|
572
|
+
operation: result.operation,
|
|
573
|
+
resourceType: result.resourceType,
|
|
574
|
+
path,
|
|
575
|
+
}))
|
|
576
|
+
)
|
|
577
|
+
case "table": {
|
|
578
|
+
const lines: string[] = []
|
|
579
|
+
lines.push(`[DRY RUN] Would ${result.operation} ${result.count} ${result.resourceType}(s):`)
|
|
580
|
+
lines.push("")
|
|
581
|
+
for (const path of result.paths) {
|
|
582
|
+
lines.push(` ${path}`)
|
|
583
|
+
}
|
|
584
|
+
return lines.join("\n")
|
|
585
|
+
}
|
|
586
|
+
default:
|
|
587
|
+
const _exhaustive: never = format
|
|
588
|
+
throw new Error(`Unknown format: ${_exhaustive}`)
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Print dry-run output to stdout.
|
|
594
|
+
*
|
|
595
|
+
* @param result - The dry-run result
|
|
596
|
+
* @param format - Output format
|
|
597
|
+
*/
|
|
598
|
+
export function printDryRunOutput(
|
|
599
|
+
result: DryRunResult,
|
|
600
|
+
format: OutputFormat
|
|
601
|
+
): void {
|
|
602
|
+
console.log(formatDryRunOutput(result, format))
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* Create a DryRunResult from a list of paths.
|
|
607
|
+
*
|
|
608
|
+
* @param paths - List of file paths
|
|
609
|
+
* @param operation - The operation being performed
|
|
610
|
+
* @param resourceType - The type of resource
|
|
611
|
+
* @returns DryRunResult object
|
|
612
|
+
*/
|
|
613
|
+
export function createDryRunResult(
|
|
614
|
+
paths: string[],
|
|
615
|
+
operation: DryRunResult["operation"],
|
|
616
|
+
resourceType: DryRunResult["resourceType"]
|
|
617
|
+
): DryRunResult {
|
|
618
|
+
return {
|
|
619
|
+
paths,
|
|
620
|
+
operation,
|
|
621
|
+
resourceType,
|
|
622
|
+
count: paths.length,
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// ========================
|
|
627
|
+
// Re-exports for convenience
|
|
628
|
+
// ========================
|
|
629
|
+
|
|
630
|
+
export {
|
|
631
|
+
// JSON formatter exports
|
|
632
|
+
formatJson,
|
|
633
|
+
formatJsonArraySuccess,
|
|
634
|
+
formatJsonError,
|
|
635
|
+
formatJsonSuccess,
|
|
636
|
+
printJson,
|
|
637
|
+
printJsonArraySuccess,
|
|
638
|
+
printJsonError,
|
|
639
|
+
printJsonSuccess,
|
|
640
|
+
type JsonFormatOptions,
|
|
641
|
+
type JsonResponse,
|
|
642
|
+
} from "./formatters/json"
|
|
643
|
+
|
|
644
|
+
export { formatNdjson, printNdjson } from "./formatters/ndjson"
|
|
645
|
+
|
|
646
|
+
export {
|
|
647
|
+
formatAggregateTokenSummary,
|
|
648
|
+
formatChatSearchTable,
|
|
649
|
+
formatChatTable,
|
|
650
|
+
formatProjectsTable,
|
|
651
|
+
formatSessionsTable,
|
|
652
|
+
formatTokenSummary,
|
|
653
|
+
printAggregateTokenSummary,
|
|
654
|
+
printChatSearchTable,
|
|
655
|
+
printChatTable,
|
|
656
|
+
printProjectsTable,
|
|
657
|
+
printSessionsTable,
|
|
658
|
+
printTokenSummary,
|
|
659
|
+
type IndexedChatSearchResult,
|
|
660
|
+
type TableFormatOptions,
|
|
661
|
+
} from "./formatters/table"
|