From b7b7b2c7d661dbbb75df64b56ae93863b6018729 Mon Sep 17 00:00:00 2001 From: AgentSeal Date: Wed, 15 Apr 2026 03:59:49 -0700 Subject: [PATCH] perf: lazy-load cursor provider to eliminate startup overhead Cursor module (sqlite.ts, better-sqlite3) now only loads when cursor provider is actually requested. Claude/Codex startup is unaffected -- cursor import never happens unless needed. --- src/cli.ts | 4 ++-- src/dashboard.tsx | 16 +++++++++++----- src/providers/index.ts | 34 ++++++++++++++++++++++++++++----- tests/provider-registry.test.ts | 16 +++++++++++----- tests/providers/cursor.test.ts | 10 ++++++++-- 5 files changed, 61 insertions(+), 19 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index dfe743c..8aca0a4 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -6,7 +6,7 @@ import { renderStatusBar } from './format.js' import { installMenubar, renderMenubarFormat, type PeriodData, type ProviderCost, uninstallMenubar } from './menubar.js' import { CATEGORY_LABELS, type DateRange, type ProjectSummary, type TaskCategory } from './types.js' import { renderDashboard } from './dashboard.js' -import { providers } from './providers/index.js' +import { getAllProviders } from './providers/index.js' import { readConfig, saveConfig, getConfigFilePath } from './config.js' import { createRequire } from 'node:module' @@ -130,7 +130,7 @@ program const weekData = buildPeriodData('7 Days', await parseAllSessions(getDateRange('week').range, pf)) const monthData = buildPeriodData('Month', await parseAllSessions(getDateRange('month').range, pf)) const todayProviders: ProviderCost[] = [] - for (const p of providers) { + for (const p of await getAllProviders()) { const data = await parseAllSessions(todayRange, p.name) const cost = data.reduce((s, proj) => s + proj.totalCostUSD, 0) if (cost > 0) todayProviders.push({ name: p.displayName, cost }) diff --git a/src/dashboard.tsx b/src/dashboard.tsx index 4a7f725..6a09b5e 100644 --- a/src/dashboard.tsx +++ b/src/dashboard.tsx @@ -6,7 +6,7 @@ import { CATEGORY_LABELS, type ProjectSummary, type TaskCategory } from './types import { formatCost, formatTokens } from './format.js' import { parseAllSessions } from './parser.js' import { loadPricing } from './models.js' -import { providers } from './providers/index.js' +import { getAllProviders } from './providers/index.js' type Period = 'today' | 'week' | 'month' | '30days' | '90days' @@ -391,10 +391,15 @@ function BashBreakdown({ projects, pw, bw }: { projects: ProjectSummary[]; pw: n ) } +const PROVIDER_DISPLAY_NAMES: Record = { + all: 'All', + claude: 'Claude', + codex: 'Codex', + cursor: 'Cursor', +} + function getProviderDisplayName(name: string): string { - if (name === 'all') return 'All' - const provider = providers.find(p => p.name === name) - return provider?.displayName ?? name + return PROVIDER_DISPLAY_NAMES[name] ?? name } function PeriodTabs({ active, providerName, showProvider }: { @@ -514,7 +519,8 @@ function InteractiveDashboard({ initialProjects, initialPeriod, initialProvider, let cancelled = false async function detect() { const found: string[] = [] - for (const p of providers) { + const allProviders = await getAllProviders() + for (const p of allProviders) { const sessions = await p.discoverSessions() if (sessions.length > 0) found.push(p.name) } diff --git a/src/providers/index.ts b/src/providers/index.ts index a3b226d..21ff05c 100644 --- a/src/providers/index.ts +++ b/src/providers/index.ts @@ -1,14 +1,36 @@ import { claude } from './claude.js' import { codex } from './codex.js' -import { cursor } from './cursor.js' import type { Provider, SessionSource } from './types.js' -export const providers: Provider[] = [claude, codex, cursor] +let cursorProvider: Provider | null = null +let cursorLoadAttempted = false + +async function loadCursor(): Promise { + if (cursorLoadAttempted) return cursorProvider + cursorLoadAttempted = true + try { + const { cursor } = await import('./cursor.js') + cursorProvider = cursor + return cursor + } catch { + return null + } +} + +const coreProviders: Provider[] = [claude, codex] + +export async function getAllProviders(): Promise { + const cursor = await loadCursor() + return cursor ? [...coreProviders, cursor] : [...coreProviders] +} + +export const providers = coreProviders export async function discoverAllSessions(providerFilter?: string): Promise { + const allProviders = await getAllProviders() const filtered = providerFilter && providerFilter !== 'all' - ? providers.filter(p => p.name === providerFilter) - : providers + ? allProviders.filter(p => p.name === providerFilter) + : allProviders const all: SessionSource[] = [] for (const provider of filtered) { const sessions = await provider.discoverSessions() @@ -18,6 +40,8 @@ export async function discoverAllSessions(providerFilter?: string): Promise p.name === name) + return cursorProvider?.name === name + ? cursorProvider + : coreProviders.find(p => p.name === name) } diff --git a/tests/provider-registry.test.ts b/tests/provider-registry.test.ts index ad3d5b4..bbdf1b0 100644 --- a/tests/provider-registry.test.ts +++ b/tests/provider-registry.test.ts @@ -1,9 +1,14 @@ import { describe, it, expect } from 'vitest' -import { providers } from '../src/providers/index.js' +import { providers, getAllProviders } from '../src/providers/index.js' describe('provider registry', () => { - it('has all providers registered', () => { - expect(providers.map(p => p.name)).toEqual(['claude', 'codex', 'cursor']) + it('has core providers registered synchronously', () => { + expect(providers.map(p => p.name)).toEqual(['claude', 'codex']) + }) + + it('includes cursor after async load', async () => { + const all = await getAllProviders() + expect(all.map(p => p.name)).toEqual(['claude', 'codex', 'cursor']) }) it('claude tool display names are identity', () => { @@ -33,8 +38,9 @@ describe('provider registry', () => { expect(claude.modelDisplayName('claude-sonnet-4-6')).toBe('Sonnet 4.6') }) - it('cursor model display names handle auto mode', () => { - const cursor = providers.find(p => p.name === 'cursor')! + it('cursor model display names handle auto mode', async () => { + const all = await getAllProviders() + const cursor = all.find(p => p.name === 'cursor')! expect(cursor.modelDisplayName('default')).toBe('Auto (Sonnet est.)') expect(cursor.modelDisplayName('claude-4.5-opus-high-thinking')).toBe('Opus 4.5 (Thinking)') expect(cursor.modelDisplayName('grok-code-fast-1')).toBe('Grok Code Fast') diff --git a/tests/providers/cursor.test.ts b/tests/providers/cursor.test.ts index 07b5f53..5a27484 100644 --- a/tests/providers/cursor.test.ts +++ b/tests/providers/cursor.test.ts @@ -1,7 +1,13 @@ import { describe, it, expect, vi, beforeEach } from 'vitest' -import { providers } from '../../src/providers/index.js' +import { getAllProviders } from '../../src/providers/index.js' +import type { Provider } from '../../src/providers/types.js' -const cursorProvider = providers.find(p => p.name === 'cursor')! +let cursorProvider: Provider + +beforeEach(async () => { + const all = await getAllProviders() + cursorProvider = all.find(p => p.name === 'cursor')! +}) describe('cursor provider', () => { it('is registered', () => {