mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-05-17 03:56:45 +00:00
Fix Gemini provider for JSONL format (CLI 0.39+)
Gemini CLI 0.39 switched from single JSON to JSONL with one object per line and $set metadata lines. Parser now handles both formats. Also updated --provider help text to list all providers.
This commit is contained in:
parent
220d3193db
commit
64259c929c
2 changed files with 53 additions and 12 deletions
14
src/cli.ts
14
src/cli.ts
|
|
@ -286,7 +286,7 @@ program
|
|||
.option('-p, --period <period>', 'Starting period: today, week, 30days, month, all', 'week')
|
||||
.option('--from <date>', 'Start date (YYYY-MM-DD). Overrides --period when set')
|
||||
.option('--to <date>', 'End date (YYYY-MM-DD). Overrides --period when set')
|
||||
.option('--provider <provider>', 'Filter by provider: all, claude, codex, cursor', 'all')
|
||||
.option('--provider <provider>', 'Filter by provider (e.g. claude, gemini, cursor, copilot)', 'all')
|
||||
.option('--format <format>', 'Output format: tui, json', 'tui')
|
||||
.option('--project <name>', 'Show only projects matching name (repeatable)', collect, [])
|
||||
.option('--exclude <name>', 'Exclude projects matching name (repeatable)', collect, [])
|
||||
|
|
@ -364,7 +364,7 @@ program
|
|||
.command('status')
|
||||
.description('Compact status output (today + week + month)')
|
||||
.option('--format <format>', 'Output format: terminal, menubar-json, json', 'terminal')
|
||||
.option('--provider <provider>', 'Filter by provider: all, claude, codex, cursor', 'all')
|
||||
.option('--provider <provider>', 'Filter by provider (e.g. claude, gemini, cursor, copilot)', 'all')
|
||||
.option('--project <name>', 'Show only projects matching name (repeatable)', collect, [])
|
||||
.option('--exclude <name>', 'Exclude projects matching name (repeatable)', collect, [])
|
||||
.option('--period <period>', 'Primary period for menubar-json: today, week, 30days, month, all', 'today')
|
||||
|
|
@ -557,7 +557,7 @@ program
|
|||
program
|
||||
.command('today')
|
||||
.description('Today\'s usage dashboard')
|
||||
.option('--provider <provider>', 'Filter by provider: all, claude, codex, cursor', 'all')
|
||||
.option('--provider <provider>', 'Filter by provider (e.g. claude, gemini, cursor, copilot)', 'all')
|
||||
.option('--format <format>', 'Output format: tui, json', 'tui')
|
||||
.option('--project <name>', 'Show only projects matching name (repeatable)', collect, [])
|
||||
.option('--exclude <name>', 'Exclude projects matching name (repeatable)', collect, [])
|
||||
|
|
@ -573,7 +573,7 @@ program
|
|||
program
|
||||
.command('month')
|
||||
.description('This month\'s usage dashboard')
|
||||
.option('--provider <provider>', 'Filter by provider: all, claude, codex, cursor', 'all')
|
||||
.option('--provider <provider>', 'Filter by provider (e.g. claude, gemini, cursor, copilot)', 'all')
|
||||
.option('--format <format>', 'Output format: tui, json', 'tui')
|
||||
.option('--project <name>', 'Show only projects matching name (repeatable)', collect, [])
|
||||
.option('--exclude <name>', 'Exclude projects matching name (repeatable)', collect, [])
|
||||
|
|
@ -591,7 +591,7 @@ program
|
|||
.description('Export usage data to CSV or JSON (includes 1 day, 7 days, 30 days)')
|
||||
.option('-f, --format <format>', 'Export format: csv, json', 'csv')
|
||||
.option('-o, --output <path>', 'Output file path')
|
||||
.option('--provider <provider>', 'Filter by provider: all, claude, codex, cursor', 'all')
|
||||
.option('--provider <provider>', 'Filter by provider (e.g. claude, gemini, cursor, copilot)', 'all')
|
||||
.option('--project <name>', 'Show only projects matching name (repeatable)', collect, [])
|
||||
.option('--exclude <name>', 'Exclude projects matching name (repeatable)', collect, [])
|
||||
.action(async (opts) => {
|
||||
|
|
@ -870,7 +870,7 @@ program
|
|||
.command('optimize')
|
||||
.description('Find token waste and get exact fixes')
|
||||
.option('-p, --period <period>', 'Analysis period: today, week, 30days, month, all', '30days')
|
||||
.option('--provider <provider>', 'Filter by provider: all, claude, codex, cursor', 'all')
|
||||
.option('--provider <provider>', 'Filter by provider (e.g. claude, gemini, cursor, copilot)', 'all')
|
||||
.action(async (opts) => {
|
||||
await loadPricing()
|
||||
const { range, label } = getDateRange(opts.period)
|
||||
|
|
@ -882,7 +882,7 @@ program
|
|||
.command('compare')
|
||||
.description('Compare two AI models side-by-side')
|
||||
.option('-p, --period <period>', 'Analysis period: today, week, 30days, month, all', 'all')
|
||||
.option('--provider <provider>', 'Filter by provider: all, claude, codex, cursor', 'all')
|
||||
.option('--provider <provider>', 'Filter by provider (e.g. claude, gemini, cursor, copilot)', 'all')
|
||||
.action(async (opts) => {
|
||||
await loadPricing()
|
||||
const { range } = getDateRange(opts.period)
|
||||
|
|
|
|||
|
|
@ -144,6 +144,40 @@ function parseSession(data: GeminiSession, seenKeys: Set<string>): ParsedProvide
|
|||
return results
|
||||
}
|
||||
|
||||
function parseJsonl(raw: string): GeminiSession | null {
|
||||
const lines = raw.split('\n').filter(l => l.trim())
|
||||
if (lines.length === 0) return null
|
||||
|
||||
let sessionId = ''
|
||||
let startTime = ''
|
||||
let projectHash: string | undefined
|
||||
let lastUpdated: string | undefined
|
||||
let kind: string | undefined
|
||||
const messages: GeminiMessage[] = []
|
||||
|
||||
for (const line of lines) {
|
||||
let obj: Record<string, unknown>
|
||||
try {
|
||||
obj = JSON.parse(line)
|
||||
} catch {
|
||||
continue
|
||||
}
|
||||
if (obj['$set'] !== undefined) continue
|
||||
if (obj['sessionId'] && obj['startTime'] && !sessionId) {
|
||||
sessionId = obj['sessionId'] as string
|
||||
startTime = obj['startTime'] as string
|
||||
projectHash = obj['projectHash'] as string | undefined
|
||||
lastUpdated = obj['lastUpdated'] as string | undefined
|
||||
kind = obj['kind'] as string | undefined
|
||||
} else if (obj['id'] && obj['type']) {
|
||||
messages.push(obj as unknown as GeminiMessage)
|
||||
}
|
||||
}
|
||||
|
||||
if (!sessionId) return null
|
||||
return { sessionId, projectHash, startTime, lastUpdated, kind, messages }
|
||||
}
|
||||
|
||||
function createParser(source: SessionSource, seenKeys: Set<string>): SessionParser {
|
||||
return {
|
||||
async *parse(): AsyncGenerator<ParsedProviderCall> {
|
||||
|
|
@ -154,14 +188,21 @@ function createParser(source: SessionSource, seenKeys: Set<string>): SessionPars
|
|||
return
|
||||
}
|
||||
|
||||
let data: GeminiSession
|
||||
let data: GeminiSession | null = null
|
||||
|
||||
// Try single JSON first (Gemini CLI <=0.38), then JSONL (>=0.39)
|
||||
try {
|
||||
data = JSON.parse(raw)
|
||||
} catch {
|
||||
return
|
||||
const parsed = JSON.parse(raw)
|
||||
if (parsed.messages && parsed.sessionId) {
|
||||
data = parsed
|
||||
}
|
||||
} catch { /* not single JSON */ }
|
||||
|
||||
if (!data) {
|
||||
data = parseJsonl(raw)
|
||||
}
|
||||
|
||||
if (!data.messages || !data.sessionId) return
|
||||
if (!data?.messages || !data.sessionId) return
|
||||
|
||||
const calls = parseSession(data, seenKeys)
|
||||
for (const call of calls) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue