mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-05-07 13:48:40 +00:00
feat: activity classification + language breakdown for Cursor
- Extract user text from bubbles for activity classifier - Extract codeBlocks languageId for programming language breakdown - Show Languages panel instead of Core Tools/Shell/MCP for Cursor - Adaptive dashboard layout based on active provider - 120-day daily activity range for longer periods
This commit is contained in:
parent
ea5fd90a68
commit
11cdcaa89d
2 changed files with 74 additions and 15 deletions
|
|
@ -304,7 +304,7 @@ function ActivityBreakdown({ projects, pw, bw }: { projects: ProjectSummary[]; p
|
|||
)
|
||||
}
|
||||
|
||||
function ToolBreakdown({ projects, pw, bw }: { projects: ProjectSummary[]; pw: number; bw: number }) {
|
||||
function ToolBreakdown({ projects, pw, bw, title }: { projects: ProjectSummary[]; pw: number; bw: number; title?: string }) {
|
||||
const toolTotals: Record<string, number> = {}
|
||||
for (const project of projects) {
|
||||
for (const session of project.sessions) {
|
||||
|
|
@ -318,7 +318,7 @@ function ToolBreakdown({ projects, pw, bw }: { projects: ProjectSummary[]; pw: n
|
|||
const nw = Math.max(6, pw - bw - 15)
|
||||
|
||||
return (
|
||||
<Panel title="Core Tools" color={PANEL_COLORS.tools} width={pw}>
|
||||
<Panel title={title ?? 'Core Tools'} color={PANEL_COLORS.tools} width={pw}>
|
||||
<Text dimColor wrap="truncate-end">{''.padEnd(bw + 1 + nw)}{'calls'.padStart(7)}</Text>
|
||||
{sorted.slice(0, 10).map(([tool, calls]) => (
|
||||
<Text key={tool} wrap="truncate-end">
|
||||
|
|
@ -462,8 +462,9 @@ function Row({ wide, width, children }: { wide: boolean; width: number; children
|
|||
return <>{children}</>
|
||||
}
|
||||
|
||||
function DashboardContent({ projects, period, columns }: { projects: ProjectSummary[]; period: Period; columns?: number }) {
|
||||
function DashboardContent({ projects, period, columns, activeProvider }: { projects: ProjectSummary[]; period: Period; columns?: number; activeProvider?: string }) {
|
||||
const { dashWidth, wide, halfWidth, barWidth } = getLayout(columns)
|
||||
const isCursor = activeProvider === 'cursor'
|
||||
|
||||
if (projects.length === 0) {
|
||||
return (
|
||||
|
|
@ -474,13 +475,14 @@ function DashboardContent({ projects, period, columns }: { projects: ProjectSumm
|
|||
}
|
||||
|
||||
const pw = wide ? halfWidth : dashWidth
|
||||
const days = period === 'month' || period === '30days' ? 31 : period === '120days' ? 120 : 14
|
||||
|
||||
return (
|
||||
<Box flexDirection="column" width={dashWidth}>
|
||||
<Overview projects={projects} label={PERIOD_LABELS[period]} width={dashWidth} />
|
||||
|
||||
<Row wide={wide} width={dashWidth}>
|
||||
<DailyActivity projects={projects} days={period === 'month' || period === '30days' ? 31 : 14} pw={pw} bw={barWidth} />
|
||||
<DailyActivity projects={projects} days={days} pw={pw} bw={barWidth} />
|
||||
<ProjectBreakdown projects={projects} pw={pw} bw={barWidth} />
|
||||
</Row>
|
||||
|
||||
|
|
@ -489,12 +491,17 @@ function DashboardContent({ projects, period, columns }: { projects: ProjectSumm
|
|||
<ModelBreakdown projects={projects} pw={pw} bw={barWidth} />
|
||||
</Row>
|
||||
|
||||
<Row wide={wide} width={dashWidth}>
|
||||
<ToolBreakdown projects={projects} pw={pw} bw={barWidth} />
|
||||
<BashBreakdown projects={projects} pw={pw} bw={barWidth} />
|
||||
</Row>
|
||||
|
||||
<McpBreakdown projects={projects} pw={dashWidth} bw={barWidth} />
|
||||
{isCursor ? (
|
||||
<ToolBreakdown projects={projects} pw={dashWidth} bw={barWidth} title="Languages" />
|
||||
) : (
|
||||
<>
|
||||
<Row wide={wide} width={dashWidth}>
|
||||
<ToolBreakdown projects={projects} pw={pw} bw={barWidth} />
|
||||
<BashBreakdown projects={projects} pw={pw} bw={barWidth} />
|
||||
</Row>
|
||||
<McpBreakdown projects={projects} pw={dashWidth} bw={barWidth} />
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
|
@ -607,7 +614,7 @@ function InteractiveDashboard({ initialProjects, initialPeriod, initialProvider,
|
|||
return (
|
||||
<Box flexDirection="column" width={dashWidth}>
|
||||
<PeriodTabs active={period} providerName={activeProvider} showProvider={multipleProviders} />
|
||||
<DashboardContent projects={projects} period={period} columns={columns} />
|
||||
<DashboardContent projects={projects} period={period} columns={columns} activeProvider={activeProvider} />
|
||||
<StatusBar width={dashWidth} showProvider={multipleProviders} />
|
||||
</Box>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ type BubbleRow = {
|
|||
model: string | null
|
||||
created_at: string | null
|
||||
conversation_id: string | null
|
||||
user_text: string | null
|
||||
code_blocks: string | null
|
||||
}
|
||||
|
||||
function getCursorDbPath(): string {
|
||||
|
|
@ -41,6 +43,25 @@ function getCursorDbPath(): string {
|
|||
return join(homedir(), '.config', 'Cursor', 'User', 'globalStorage', 'state.vscdb')
|
||||
}
|
||||
|
||||
type CodeBlock = { languageId?: string }
|
||||
|
||||
function extractLanguages(codeBlocksJson: string | null): string[] {
|
||||
if (!codeBlocksJson) return []
|
||||
try {
|
||||
const blocks = JSON.parse(codeBlocksJson) as CodeBlock[]
|
||||
if (!Array.isArray(blocks)) return []
|
||||
const langs = new Set<string>()
|
||||
for (const block of blocks) {
|
||||
if (block.languageId && block.languageId !== 'plaintext') {
|
||||
langs.add(block.languageId)
|
||||
}
|
||||
}
|
||||
return [...langs]
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
function resolveModel(raw: string | null): string {
|
||||
if (!raw || raw === 'default') return CURSOR_DEFAULT_MODEL
|
||||
return raw
|
||||
|
|
@ -57,7 +78,9 @@ const BUBBLE_QUERY_BASE = `
|
|||
json_extract(value, '$.tokenCount.outputTokens') as output_tokens,
|
||||
json_extract(value, '$.modelInfo.modelName') as model,
|
||||
json_extract(value, '$.createdAt') as created_at,
|
||||
json_extract(value, '$.conversationId') as conversation_id
|
||||
json_extract(value, '$.conversationId') as conversation_id,
|
||||
substr(json_extract(value, '$.text'), 1, 500) as user_text,
|
||||
json_extract(value, '$.codeBlocks') as code_blocks
|
||||
FROM cursorDiskKV
|
||||
WHERE key LIKE 'bubbleId:%'
|
||||
AND json_extract(value, '$.tokenCount.inputTokens') > 0
|
||||
|
|
@ -112,6 +135,9 @@ function parseBubbles(db: SqliteDatabase, seenKeys: Set<string>): { calls: Parse
|
|||
const costUSD = calculateCost(pricingModel, inputTokens, outputTokens, 0, 0, 0)
|
||||
|
||||
const timestamp = createdAt || ''
|
||||
const userText = row.user_text ?? ''
|
||||
|
||||
const languages = extractLanguages(row.code_blocks)
|
||||
|
||||
results.push({
|
||||
provider: 'cursor',
|
||||
|
|
@ -124,11 +150,11 @@ function parseBubbles(db: SqliteDatabase, seenKeys: Set<string>): { calls: Parse
|
|||
reasoningTokens: 0,
|
||||
webSearchRequests: 0,
|
||||
costUSD,
|
||||
tools: [],
|
||||
tools: languages,
|
||||
timestamp,
|
||||
speed: 'standard',
|
||||
deduplicationKey: dedupKey,
|
||||
userMessage: '',
|
||||
userMessage: userText,
|
||||
sessionId: conversationId,
|
||||
})
|
||||
} catch {
|
||||
|
|
@ -189,7 +215,33 @@ export function createCursorProvider(dbPathOverride?: string): Provider {
|
|||
},
|
||||
|
||||
toolDisplayName(rawTool: string): string {
|
||||
return rawTool
|
||||
const langNames: Record<string, string> = {
|
||||
javascript: 'JavaScript',
|
||||
typescript: 'TypeScript',
|
||||
python: 'Python',
|
||||
rust: 'Rust',
|
||||
go: 'Go',
|
||||
java: 'Java',
|
||||
cpp: 'C++',
|
||||
c: 'C',
|
||||
csharp: 'C#',
|
||||
ruby: 'Ruby',
|
||||
php: 'PHP',
|
||||
swift: 'Swift',
|
||||
kotlin: 'Kotlin',
|
||||
html: 'HTML',
|
||||
css: 'CSS',
|
||||
scss: 'SCSS',
|
||||
json: 'JSON',
|
||||
yaml: 'YAML',
|
||||
markdown: 'Markdown',
|
||||
sql: 'SQL',
|
||||
shell: 'Shell',
|
||||
bash: 'Bash',
|
||||
dockerfile: 'Dockerfile',
|
||||
toml: 'TOML',
|
||||
}
|
||||
return langNames[rawTool] ?? rawTool
|
||||
},
|
||||
|
||||
async discoverSessions(): Promise<SessionSource[]> {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue