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.
This commit is contained in:
AgentSeal 2026-04-15 03:59:49 -07:00
parent 3c439cb28f
commit b7b7b2c7d6
5 changed files with 61 additions and 19 deletions

View file

@ -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 })

View file

@ -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<string, string> = {
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)
}

View file

@ -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<Provider | null> {
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<Provider[]> {
const cursor = await loadCursor()
return cursor ? [...coreProviders, cursor] : [...coreProviders]
}
export const providers = coreProviders
export async function discoverAllSessions(providerFilter?: string): Promise<SessionSource[]> {
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<Sess
}
export function getProvider(name: string): Provider | undefined {
return providers.find(p => p.name === name)
return cursorProvider?.name === name
? cursorProvider
: coreProviders.find(p => p.name === name)
}

View file

@ -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')

View file

@ -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', () => {