export type Tool = 'claude-code' | 'opencode' | 'crush' | 'pi' | 'kimi' | string export type Host = 'laptop' | 'workpc' | string export interface SessionDTO { owner: string tool: string host: string session_id: string started_at: number // unix epoch seconds ended_at: number working_dir?: string source_file: string summary: string turn_count: number tokens_in_total: number tokens_out_total: number model?: string metadata?: unknown } export interface Session { id: string // `${tool}/${host}/${session_id}` tool: string host: string cwd: string // working_dir ?? '' model?: string started: string // ISO 8601 ended: string | null // null if ended_at <= started_at summary: string turns: number tokensIn: number tokensOut: number hasError: boolean // TODO: always false until error tracking is implemented } export function adaptSession(d: SessionDTO): Session { return { id: `${d.tool}/${d.host}/${d.session_id}`, tool: d.tool, host: d.host, cwd: d.working_dir ?? '', model: d.model, started: new Date(d.started_at * 1000).toISOString(), ended: d.ended_at > d.started_at ? new Date(d.ended_at * 1000).toISOString() : null, summary: d.summary, turns: d.turn_count, tokensIn: d.tokens_in_total, tokensOut: d.tokens_out_total, hasError: false, } } // ── Projects ───────────────────────────────────────────────────────────────── export interface ProjectDTO { cwd: string sessions: number turn_count: number tokens_in_total: number tokens_out_total: number last_active: number // unix epoch seconds; 0 = never hosts: string[] tools: string[] top_tool: string } export interface Project { cwd: string sessions: number turns: number tokensIn: number tokensOut: number lastActive: string // ISO 8601, or '' when last_active === 0 hosts: string[] tools: string[] topTool: string } export function adaptProject(d: ProjectDTO): Project { return { cwd: d.cwd, sessions: d.sessions, turns: d.turn_count, tokensIn: d.tokens_in_total, tokensOut: d.tokens_out_total, lastActive: d.last_active === 0 ? '' : new Date(d.last_active * 1000).toISOString(), hosts: d.hosts, tools: d.tools, topTool: d.top_tool, } } // ── Stats ───────────────────────────────────────────────────────────────────── export interface ToolRollupDTO { tool: string sessions: number turns: number tokens_in: number tokens_out: number daily_sparkline: number[] } export interface DailyBucketDTO { date_unix: number per_tool: Record } export interface HeatmapCellDTO { date_unix: number count: number } export interface CwdRowDTO { cwd: string count: number } export interface HourBucketDTO { hour: number count: number } export interface HostRowDTO { host: string count: number } export interface StatsDTO { per_tool: ToolRollupDTO[] daily: DailyBucketDTO[] heatmap: HeatmapCellDTO[] top_cwd: CwdRowDTO[] hour_of_day: HourBucketDTO[] host_split: HostRowDTO[] } export interface ToolRollup { tool: string sessions: number turns: number tokensIn: number tokensOut: number dailySparkline: number[] } export interface DailyBucket { date: string // ISO 8601 perTool: Record } export interface HeatmapCell { date: string // ISO 8601 count: number } export interface CwdRow { cwd: string count: number } export interface HourBucket { hour: number count: number } export interface HostRow { host: string count: number } export interface Stats { perTool: ToolRollup[] daily: DailyBucket[] heatmap: HeatmapCell[] topCwd: CwdRow[] hourOfDay: HourBucket[] hostSplit: HostRow[] } export function adaptStats(d: StatsDTO): Stats { return { perTool: d.per_tool.map(r => ({ tool: r.tool, sessions: r.sessions, turns: r.turns, tokensIn: r.tokens_in, tokensOut: r.tokens_out, dailySparkline: r.daily_sparkline, })), daily: d.daily.map(b => ({ date: new Date(b.date_unix * 1000).toISOString(), perTool: b.per_tool, })), heatmap: d.heatmap.map(c => ({ date: new Date(c.date_unix * 1000).toISOString(), count: c.count, })), topCwd: d.top_cwd, hourOfDay: d.hour_of_day, hostSplit: d.host_split, } }