@oh-my-pi-zen/omp-stats 16.3.6-zen.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/CHANGELOG.md +197 -0
  2. package/README.md +82 -0
  3. package/build.ts +95 -0
  4. package/dist/client/index.css +1 -0
  5. package/dist/client/index.html +24 -0
  6. package/dist/client/index.js +257 -0
  7. package/dist/client/styles.css +1656 -0
  8. package/dist/types/aggregator.d.ts +87 -0
  9. package/dist/types/client/App.d.ts +1 -0
  10. package/dist/types/client/api.d.ts +21 -0
  11. package/dist/types/client/app/AppLayout.d.ts +16 -0
  12. package/dist/types/client/app/NavRail.d.ts +7 -0
  13. package/dist/types/client/app/RangeControl.d.ts +7 -0
  14. package/dist/types/client/app/SyncButton.d.ts +14 -0
  15. package/dist/types/client/app/ThemeToggle.d.ts +1 -0
  16. package/dist/types/client/app/TopBar.d.ts +15 -0
  17. package/dist/types/client/app/routes.d.ts +12 -0
  18. package/dist/types/client/components/AgentTokenShare.d.ts +5 -0
  19. package/dist/types/client/components/chart-shared.d.ts +173 -0
  20. package/dist/types/client/components/models-table-shared.d.ts +175 -0
  21. package/dist/types/client/components/range-meta.d.ts +21 -0
  22. package/dist/types/client/data/charts.d.ts +1 -0
  23. package/dist/types/client/data/formatters.d.ts +8 -0
  24. package/dist/types/client/data/useHashRoute.d.ts +8 -0
  25. package/dist/types/client/data/useResource.d.ts +13 -0
  26. package/dist/types/client/data/view-models.d.ts +67 -0
  27. package/dist/types/client/index.d.ts +2 -0
  28. package/dist/types/client/routes/BehaviorRoute.d.ts +7 -0
  29. package/dist/types/client/routes/CostsRoute.d.ts +7 -0
  30. package/dist/types/client/routes/ErrorsRoute.d.ts +8 -0
  31. package/dist/types/client/routes/GainRoute.d.ts +7 -0
  32. package/dist/types/client/routes/ModelsRoute.d.ts +7 -0
  33. package/dist/types/client/routes/OverviewRoute.d.ts +8 -0
  34. package/dist/types/client/routes/ProjectsRoute.d.ts +7 -0
  35. package/dist/types/client/routes/RequestsRoute.d.ts +8 -0
  36. package/dist/types/client/routes/ToolsRoute.d.ts +7 -0
  37. package/dist/types/client/routes/index.d.ts +9 -0
  38. package/dist/types/client/types.d.ts +63 -0
  39. package/dist/types/client/ui/AsyncBoundary.d.ts +12 -0
  40. package/dist/types/client/ui/DataTable.d.ts +17 -0
  41. package/dist/types/client/ui/EmptyState.d.ts +7 -0
  42. package/dist/types/client/ui/ErrorState.d.ts +6 -0
  43. package/dist/types/client/ui/JsonBlock.d.ts +7 -0
  44. package/dist/types/client/ui/MetricCluster.d.ts +5 -0
  45. package/dist/types/client/ui/Panel.d.ts +7 -0
  46. package/dist/types/client/ui/RequestDrawer.d.ts +5 -0
  47. package/dist/types/client/ui/SegmentedControl.d.ts +12 -0
  48. package/dist/types/client/ui/Skeleton.d.ts +8 -0
  49. package/dist/types/client/ui/StatusPill.d.ts +7 -0
  50. package/dist/types/client/ui/index.d.ts +11 -0
  51. package/dist/types/client/useSystemTheme.d.ts +11 -0
  52. package/dist/types/db.d.ts +144 -0
  53. package/dist/types/embedded-client.d.ts +18 -0
  54. package/dist/types/gain-aggregator.d.ts +26 -0
  55. package/dist/types/index.d.ts +7 -0
  56. package/dist/types/parser.d.ts +53 -0
  57. package/dist/types/server.d.ts +7 -0
  58. package/dist/types/shared-types.d.ts +301 -0
  59. package/dist/types/sync-worker.d.ts +31 -0
  60. package/dist/types/types.d.ts +164 -0
  61. package/dist/types/user-metrics.d.ts +72 -0
  62. package/package.json +95 -0
  63. package/src/aggregator.ts +501 -0
  64. package/src/client/App.tsx +109 -0
  65. package/src/client/api.ts +102 -0
  66. package/src/client/app/AppLayout.tsx +93 -0
  67. package/src/client/app/NavRail.tsx +44 -0
  68. package/src/client/app/RangeControl.tsx +39 -0
  69. package/src/client/app/SyncButton.tsx +75 -0
  70. package/src/client/app/ThemeToggle.tsx +37 -0
  71. package/src/client/app/TopBar.tsx +73 -0
  72. package/src/client/app/routes.ts +69 -0
  73. package/src/client/components/AgentTokenShare.tsx +68 -0
  74. package/src/client/components/chart-shared.tsx +257 -0
  75. package/src/client/components/models-table-shared.tsx +255 -0
  76. package/src/client/components/range-meta.ts +73 -0
  77. package/src/client/css.d.ts +1 -0
  78. package/src/client/data/charts.ts +14 -0
  79. package/src/client/data/formatters.ts +45 -0
  80. package/src/client/data/useHashRoute.ts +87 -0
  81. package/src/client/data/useResource.ts +154 -0
  82. package/src/client/data/view-models.ts +251 -0
  83. package/src/client/index.tsx +10 -0
  84. package/src/client/routes/BehaviorRoute.tsx +623 -0
  85. package/src/client/routes/CostsRoute.tsx +234 -0
  86. package/src/client/routes/ErrorsRoute.tsx +118 -0
  87. package/src/client/routes/GainRoute.tsx +226 -0
  88. package/src/client/routes/ModelsRoute.tsx +430 -0
  89. package/src/client/routes/OverviewRoute.tsx +342 -0
  90. package/src/client/routes/ProjectsRoute.tsx +163 -0
  91. package/src/client/routes/RequestsRoute.tsx +123 -0
  92. package/src/client/routes/ToolsRoute.tsx +463 -0
  93. package/src/client/routes/index.ts +9 -0
  94. package/src/client/styles.css +1330 -0
  95. package/src/client/types.ts +80 -0
  96. package/src/client/ui/AsyncBoundary.tsx +54 -0
  97. package/src/client/ui/DataTable.tsx +122 -0
  98. package/src/client/ui/EmptyState.tsx +16 -0
  99. package/src/client/ui/ErrorState.tsx +25 -0
  100. package/src/client/ui/JsonBlock.tsx +75 -0
  101. package/src/client/ui/MetricCluster.tsx +67 -0
  102. package/src/client/ui/Panel.tsx +24 -0
  103. package/src/client/ui/RequestDrawer.tsx +208 -0
  104. package/src/client/ui/SegmentedControl.tsx +36 -0
  105. package/src/client/ui/Skeleton.tsx +17 -0
  106. package/src/client/ui/StatusPill.tsx +15 -0
  107. package/src/client/ui/index.ts +11 -0
  108. package/src/client/useSystemTheme.ts +87 -0
  109. package/src/db.ts +1530 -0
  110. package/src/embedded-client.generated.txt +0 -0
  111. package/src/embedded-client.ts +26 -0
  112. package/src/gain-aggregator.ts +281 -0
  113. package/src/index.ts +194 -0
  114. package/src/parser.ts +450 -0
  115. package/src/server.ts +352 -0
  116. package/src/shared-types.ts +323 -0
  117. package/src/sync-worker.ts +40 -0
  118. package/src/types.ts +171 -0
  119. package/src/user-metrics.ts +685 -0
  120. package/tailwind.config.js +40 -0
@@ -0,0 +1,2 @@
1
+ import "./styles.css";
2
+ import "./data/charts";
@@ -0,0 +1,7 @@
1
+ import type { TimeRange } from "../types";
2
+ export interface BehaviorRouteProps {
3
+ active: boolean;
4
+ range: TimeRange;
5
+ refreshTrigger: number;
6
+ }
7
+ export declare function BehaviorRoute({ active, range, refreshTrigger }: BehaviorRouteProps): import("react").JSX.Element;
@@ -0,0 +1,7 @@
1
+ import type { TimeRange } from "../types";
2
+ export interface CostsRouteProps {
3
+ active: boolean;
4
+ range: TimeRange;
5
+ refreshTrigger: number;
6
+ }
7
+ export declare function CostsRoute({ active, range, refreshTrigger }: CostsRouteProps): import("react").JSX.Element;
@@ -0,0 +1,8 @@
1
+ import type { TimeRange } from "../types";
2
+ export interface ErrorsRouteProps {
3
+ active: boolean;
4
+ range: TimeRange;
5
+ refreshTrigger: number;
6
+ onRequestClick: (id: number) => void;
7
+ }
8
+ export declare function ErrorsRoute({ active, refreshTrigger, onRequestClick }: ErrorsRouteProps): import("react").JSX.Element;
@@ -0,0 +1,7 @@
1
+ import type { TimeRange } from "../types";
2
+ export interface GainRouteProps {
3
+ active: boolean;
4
+ range: TimeRange;
5
+ refreshTrigger: number;
6
+ }
7
+ export declare function GainRoute({ active, range, refreshTrigger }: GainRouteProps): import("react").JSX.Element;
@@ -0,0 +1,7 @@
1
+ import type { TimeRange } from "../types";
2
+ export interface ModelsRouteProps {
3
+ active: boolean;
4
+ range: TimeRange;
5
+ refreshTrigger: number;
6
+ }
7
+ export declare function ModelsRoute({ active, range, refreshTrigger }: ModelsRouteProps): import("react").JSX.Element;
@@ -0,0 +1,8 @@
1
+ import type { TimeRange } from "../types";
2
+ export interface OverviewRouteProps {
3
+ active: boolean;
4
+ range: TimeRange;
5
+ refreshTrigger: number;
6
+ onRequestClick: (id: number) => void;
7
+ }
8
+ export declare function OverviewRoute({ active, range, refreshTrigger, onRequestClick }: OverviewRouteProps): import("react").JSX.Element;
@@ -0,0 +1,7 @@
1
+ import type { TimeRange } from "../types";
2
+ export interface ProjectsRouteProps {
3
+ active: boolean;
4
+ range: TimeRange;
5
+ refreshTrigger: number;
6
+ }
7
+ export declare function ProjectsRoute({ active, range, refreshTrigger }: ProjectsRouteProps): import("react").JSX.Element;
@@ -0,0 +1,8 @@
1
+ import type { TimeRange } from "../types";
2
+ export interface RequestsRouteProps {
3
+ active: boolean;
4
+ range: TimeRange;
5
+ refreshTrigger: number;
6
+ onRequestClick: (id: number) => void;
7
+ }
8
+ export declare function RequestsRoute({ active, refreshTrigger, onRequestClick }: RequestsRouteProps): import("react").JSX.Element;
@@ -0,0 +1,7 @@
1
+ import type { TimeRange } from "../types";
2
+ export interface ToolsRouteProps {
3
+ active: boolean;
4
+ range: TimeRange;
5
+ refreshTrigger: number;
6
+ }
7
+ export declare function ToolsRoute({ active, range, refreshTrigger }: ToolsRouteProps): import("react").JSX.Element;
@@ -0,0 +1,9 @@
1
+ export * from "./BehaviorRoute";
2
+ export * from "./CostsRoute";
3
+ export * from "./ErrorsRoute";
4
+ export * from "./GainRoute";
5
+ export * from "./ModelsRoute";
6
+ export * from "./OverviewRoute";
7
+ export * from "./ProjectsRoute";
8
+ export * from "./RequestsRoute";
9
+ export * from "./ToolsRoute";
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Client-side type definitions.
3
+ *
4
+ * Shared shapes (aggregations, time-series, dashboard payloads) live in
5
+ * `../shared-types` and are re-exported here. The types declared inline below
6
+ * are deliberately client-only because:
7
+ * - `Usage` is redeclared locally so the client bundle avoids importing
8
+ * `@oh-my-pi-zen/pi-ai` (the server-side AI types package).
9
+ * - `MessageStats.stopReason` is widened from the server's `StopReason`
10
+ * enum to `string`, again to keep the client free of pi-ai types.
11
+ * - `TimeRange`, `OverviewStats`, `ModelDashboardStats`,
12
+ * `CostDashboardStats` are UI-only view shapes the server never produces.
13
+ */
14
+ import type { AgentTypeStats, AggregatedStats, CostTimeSeriesPoint, ModelPerformancePoint, ModelStats, ModelTimeSeriesPoint, TimeSeriesPoint } from "../shared-types";
15
+ export * from "../shared-types";
16
+ export interface Usage {
17
+ input: number;
18
+ output: number;
19
+ cacheRead: number;
20
+ cacheWrite: number;
21
+ totalTokens: number;
22
+ premiumRequests?: number;
23
+ cost: {
24
+ input: number;
25
+ output: number;
26
+ cacheRead: number;
27
+ cacheWrite: number;
28
+ total: number;
29
+ };
30
+ }
31
+ export interface MessageStats {
32
+ id?: number;
33
+ sessionFile: string;
34
+ entryId: string;
35
+ folder: string;
36
+ model: string;
37
+ provider: string;
38
+ api: string;
39
+ timestamp: number;
40
+ duration: number | null;
41
+ ttft: number | null;
42
+ stopReason: string;
43
+ errorMessage: string | null;
44
+ usage: Usage;
45
+ }
46
+ export interface RequestDetails extends MessageStats {
47
+ messages: unknown[];
48
+ output: unknown;
49
+ }
50
+ export type TimeRange = "1h" | "24h" | "7d" | "30d" | "90d" | "all";
51
+ export interface OverviewStats {
52
+ overall: AggregatedStats;
53
+ byAgentType: AgentTypeStats[];
54
+ timeSeries: TimeSeriesPoint[];
55
+ }
56
+ export interface ModelDashboardStats {
57
+ byModel: ModelStats[];
58
+ modelSeries: ModelTimeSeriesPoint[];
59
+ modelPerformanceSeries: ModelPerformancePoint[];
60
+ }
61
+ export interface CostDashboardStats {
62
+ costSeries: CostTimeSeriesPoint[];
63
+ }
@@ -0,0 +1,12 @@
1
+ import type React from "react";
2
+ export interface AsyncBoundaryProps {
3
+ loading: boolean;
4
+ error: Error | null;
5
+ data: unknown | null;
6
+ empty?: boolean;
7
+ emptyText?: string;
8
+ fallback?: React.ReactNode;
9
+ onRetry?: () => void;
10
+ children: React.ReactNode;
11
+ }
12
+ export declare function AsyncBoundary({ loading, error, data, empty, emptyText, fallback, onRetry, children, }: AsyncBoundaryProps): React.JSX.Element;
@@ -0,0 +1,17 @@
1
+ import type React from "react";
2
+ export interface DataTableColumn<T> {
3
+ key: string;
4
+ header: React.ReactNode;
5
+ render?: (item: T) => React.ReactNode;
6
+ className?: string;
7
+ numeric?: boolean;
8
+ }
9
+ export interface DataTableProps<T> {
10
+ columns: DataTableColumn<T>[];
11
+ data: T[];
12
+ keyExtractor: (item: T) => string | number;
13
+ onRowClick?: (item: T) => void;
14
+ renderMobileCard?: (item: T, onClick?: () => void) => React.ReactNode;
15
+ emptyText?: string;
16
+ }
17
+ export declare function DataTable<T>({ columns, data, keyExtractor, onRowClick, renderMobileCard, emptyText, }: DataTableProps<T>): React.JSX.Element;
@@ -0,0 +1,7 @@
1
+ import { type LucideIcon } from "lucide-react";
2
+ export interface EmptyStateProps {
3
+ message?: string;
4
+ icon?: LucideIcon;
5
+ className?: string;
6
+ }
7
+ export declare function EmptyState({ message, icon: Icon, className }: EmptyStateProps): import("react").JSX.Element;
@@ -0,0 +1,6 @@
1
+ export interface ErrorStateProps {
2
+ error?: Error | null;
3
+ onRetry?: () => void;
4
+ className?: string;
5
+ }
6
+ export declare function ErrorState({ error, onRetry, className }: ErrorStateProps): import("react").JSX.Element;
@@ -0,0 +1,7 @@
1
+ import type React from "react";
2
+ export interface JsonBlockProps {
3
+ data: unknown;
4
+ title?: string;
5
+ initialCollapsed?: boolean;
6
+ }
7
+ export declare function JsonBlock({ data, title, initialCollapsed }: JsonBlockProps): React.JSX.Element;
@@ -0,0 +1,5 @@
1
+ import type { AggregatedStats } from "../types";
2
+ export interface MetricClusterProps {
3
+ stats: AggregatedStats;
4
+ }
5
+ export declare function MetricCluster({ stats }: MetricClusterProps): import("react").JSX.Element;
@@ -0,0 +1,7 @@
1
+ import type React from "react";
2
+ export interface PanelProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "title"> {
3
+ title?: React.ReactNode;
4
+ subtitle?: React.ReactNode;
5
+ actions?: React.ReactNode;
6
+ }
7
+ export declare function Panel({ title, subtitle, actions, children, className, ...props }: PanelProps): React.JSX.Element;
@@ -0,0 +1,5 @@
1
+ export interface RequestDrawerProps {
2
+ id: number | null;
3
+ onClose: () => void;
4
+ }
5
+ export declare function RequestDrawer({ id, onClose }: RequestDrawerProps): import("react").JSX.Element | null;
@@ -0,0 +1,12 @@
1
+ export interface SegmentedControlOption<T> {
2
+ value: T;
3
+ label: string;
4
+ title?: string;
5
+ }
6
+ export interface SegmentedControlProps<T> {
7
+ options: SegmentedControlOption<T>[];
8
+ value: T;
9
+ onChange: (value: T) => void;
10
+ className?: string;
11
+ }
12
+ export declare function SegmentedControl<T>({ options, value, onChange, className }: SegmentedControlProps<T>): import("react").JSX.Element;
@@ -0,0 +1,8 @@
1
+ import type React from "react";
2
+ export interface SkeletonProps {
3
+ variant?: "text" | "rect" | "circle";
4
+ width?: string | number;
5
+ height?: string | number;
6
+ className?: string;
7
+ }
8
+ export declare function Skeleton({ variant, width, height, className }: SkeletonProps): React.JSX.Element;
@@ -0,0 +1,7 @@
1
+ import type React from "react";
2
+ export interface StatusPillProps {
3
+ variant: "success" | "danger" | "warning" | "info" | "default";
4
+ children: React.ReactNode;
5
+ className?: string;
6
+ }
7
+ export declare function StatusPill({ variant, children, className }: StatusPillProps): React.JSX.Element;
@@ -0,0 +1,11 @@
1
+ export * from "./AsyncBoundary";
2
+ export * from "./DataTable";
3
+ export * from "./EmptyState";
4
+ export * from "./ErrorState";
5
+ export * from "./JsonBlock";
6
+ export * from "./MetricCluster";
7
+ export * from "./Panel";
8
+ export * from "./RequestDrawer";
9
+ export * from "./SegmentedControl";
10
+ export * from "./Skeleton";
11
+ export * from "./StatusPill";
@@ -0,0 +1,11 @@
1
+ export type SystemTheme = "light" | "dark";
2
+ export type ThemePreference = "system" | "light" | "dark";
3
+ export declare function setThemePreference(next: ThemePreference): void;
4
+ /** Reader for the active resolved theme. Reflects system default and overrides. */
5
+ export declare function useSystemTheme(): SystemTheme;
6
+ /** Reader + writer for the theme preference (powers the toggle). */
7
+ export declare function useThemePreference(): {
8
+ preference: ThemePreference;
9
+ resolved: SystemTheme;
10
+ setPreference: (next: ThemePreference) => void;
11
+ };
@@ -0,0 +1,144 @@
1
+ import { Database } from "bun:sqlite";
2
+ import type { AgentTypeStats, AggregatedStats, BehaviorModelStats, BehaviorOverallStats, BehaviorTimeSeriesPoint, CostTimeSeriesPoint, FolderStats, MessageStats, ModelPerformancePoint, ModelStats, ModelTimeSeriesPoint, TimeSeriesPoint, ToolCallStats, ToolModelStats, ToolResultLink, ToolTimeSeriesPoint, ToolUsageStats, UserMessageLink, UserMessageStats } from "./types";
3
+ /**
4
+ * Initialize the database and create tables.
5
+ */
6
+ export declare function initDb(): Promise<Database>;
7
+ /**
8
+ * Get the stored offset for a session file.
9
+ */
10
+ export declare function getFileOffset(sessionFile: string): {
11
+ offset: number;
12
+ lastModified: number;
13
+ } | null;
14
+ /**
15
+ * Update the stored offset for a session file.
16
+ */
17
+ export declare function setFileOffset(sessionFile: string, offset: number, lastModified: number): void;
18
+ /**
19
+ * Insert message stats into the database.
20
+ *
21
+ * Forked / branched sessions (see `SessionManager.fork()` and
22
+ * `createBranchedSession()` in `@oh-my-pi-zen/pi-coding-agent`) deep-copy a parent
23
+ * session's entries into a new JSONL — same `entry_id`, `timestamp`, `model`,
24
+ * `provider`, token counts, and `responseId`. The `UNIQUE(session_file,
25
+ * entry_id)` constraint alone keys each row by file, so without the guard
26
+ * below the same provider request would land twice and inflate every
27
+ * aggregate. The `WHERE NOT EXISTS` clause skips inserts whose
28
+ * `(entry_id, timestamp)` already exists under a different `session_file` —
29
+ * first-write-wins across the lineage. Same-file re-syncs still hit the
30
+ * `ON CONFLICT(session_file, entry_id)` upsert below so historical
31
+ * `premium_requests` fix-ups continue to work.
32
+ */
33
+ export declare function insertMessageStats(stats: MessageStats[]): number;
34
+ /**
35
+ * Get overall aggregated stats.
36
+ */
37
+ export declare function getOverallStats(cutoff?: number): AggregatedStats;
38
+ /**
39
+ * Get stats grouped by model.
40
+ */
41
+ export declare function getStatsByModel(cutoff?: number): ModelStats[];
42
+ /**
43
+ * Get stats grouped by folder.
44
+ */
45
+ export declare function getStatsByFolder(cutoff?: number): FolderStats[];
46
+ /**
47
+ * Get token usage grouped by agent type (main agent, task subagents, advisor).
48
+ * Token columns are explicit so the dashboard's share denominator matches the
49
+ * counts it renders. Rows missing `agent_type` (defensive) fall back to "main".
50
+ */
51
+ export declare function getStatsByAgentType(cutoff?: number): AgentTypeStats[];
52
+ /**
53
+ * Get time series data.
54
+ */
55
+ export declare function getTimeSeries(hours?: number, cutoff?: number | null, bucketMs?: number): TimeSeriesPoint[];
56
+ /**
57
+ * Get daily performance time series data for the last N days.
58
+ */
59
+ /**
60
+ * Get daily model usage time series data for the last N days.
61
+ */
62
+ export declare function getModelTimeSeries(days?: number, cutoff?: number | null, bucketMs?: number): ModelTimeSeriesPoint[];
63
+ /**
64
+ * Get daily model performance time series data for the last N days.
65
+ */
66
+ export declare function getModelPerformanceSeries(days?: number, cutoff?: number | null, bucketMs?: number): ModelPerformancePoint[];
67
+ /**
68
+ * Get total message count.
69
+ */
70
+ export declare function getMessageCount(): number;
71
+ /**
72
+ * Close the database connection.
73
+ */
74
+ export declare function closeDb(): void;
75
+ export declare function getRecentRequests(limit?: number): MessageStats[];
76
+ export declare function getRecentErrors(limit?: number): MessageStats[];
77
+ export declare function getMessageById(id: number): MessageStats | null;
78
+ /**
79
+ * Get daily cost time series data for the last N days, broken down by model.
80
+ */
81
+ export declare function getCostTimeSeries(days?: number, cutoff?: number | null): CostTimeSeriesPoint[];
82
+ export declare function markPriorityPremiumRequestsBackfillComplete(): void;
83
+ export declare function markUserMessagesBackfillComplete(): void;
84
+ export declare function markUserMessageLinksRepairComplete(): void;
85
+ /**
86
+ * Insert user-message stats. Idempotent via UNIQUE(session_file, entry_id).
87
+ * The `WHERE NOT EXISTS` clause matches {@link insertMessageStats}: forks
88
+ * copy user entries verbatim into the child JSONL, so the same
89
+ * `(entry_id, timestamp)` must not land twice across different session files.
90
+ */
91
+ export declare function insertUserMessageStats(stats: UserMessageStats[]): number;
92
+ /**
93
+ * Backfill the responding `model`/`provider` on user-message rows that were
94
+ * persisted before their assistant reply was parsed (a side effect of
95
+ * incremental `fromOffset` syncing: the `userByEntryId` map in
96
+ * `parseSessionFile` only spans a single pass). Each row is updated at most
97
+ * once because the `model IS NULL` guard short-circuits subsequent passes.
98
+ *
99
+ * Returns the number of rows actually updated.
100
+ */
101
+ export declare function updateUserMessageLinks(links: UserMessageLink[]): number;
102
+ /**
103
+ * Daily behavioral time series, grouped by responding model+provider.
104
+ */
105
+ export declare function getBehaviorTimeSeries(cutoff?: number | null): BehaviorTimeSeriesPoint[];
106
+ /**
107
+ * Overall behavioral totals across the cutoff window.
108
+ */
109
+ export declare function getBehaviorOverall(cutoff?: number | null): BehaviorOverallStats;
110
+ /**
111
+ * Per-model behavioral totals over the cutoff window. "Unknown" represents
112
+ * user messages that never received an assistant reply.
113
+ */
114
+ export declare function getBehaviorByModel(cutoff?: number | null): BehaviorModelStats[];
115
+ /**
116
+ * Insert tool-call rows. Idempotent via UNIQUE(session_file, tool_call_id);
117
+ * the `WHERE NOT EXISTS` guard mirrors {@link insertMessageStats}: forked
118
+ * sessions deep-copy assistant entries (same `entry_id`, `timestamp`, and
119
+ * tool-call ids under a new file), so first-write-wins across the lineage
120
+ * keeps aggregates from double counting. Keyed on the assistant entry
121
+ * identity, not the call id alone — provider call ids are not a global
122
+ * namespace across unrelated sessions.
123
+ */
124
+ export declare function insertToolCalls(calls: ToolCallStats[]): number;
125
+ /**
126
+ * Attach result size / error flag to persisted tool-call rows. Results can
127
+ * land in a later incremental sync pass than the call that produced them, so
128
+ * this is an UPDATE keyed by (session_file, tool_call_id). The `IS NULL`
129
+ * guard makes re-syncs idempotent; rows skipped by the fork guard simply
130
+ * never match.
131
+ */
132
+ export declare function updateToolResults(links: ToolResultLink[]): number;
133
+ /**
134
+ * Get tool usage aggregated by tool name.
135
+ */
136
+ export declare function getToolStats(cutoff?: number): ToolUsageStats[];
137
+ /**
138
+ * Get tool usage aggregated by (tool, model, provider).
139
+ */
140
+ export declare function getToolStatsByModel(cutoff?: number): ToolModelStats[];
141
+ /**
142
+ * Get tool-call time series (one point per bucket per tool).
143
+ */
144
+ export declare function getToolTimeSeries(days?: number, cutoff?: number | null, bucketMs?: number): ToolTimeSeriesPoint[];
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Embedded stats dashboard archive handling.
3
+ *
4
+ * `embedded-client.generated.txt` holds the base64 of a gzipped tar of the
5
+ * built dashboard (`dist/client`). It is populated by
6
+ * `gen:stats` for compiled binaries and the
7
+ * prepacked npm bundle, and reset to an empty file afterwards so the dev tree
8
+ * keeps building the dashboard from source.
9
+ */
10
+ /**
11
+ * Decode the generated archive text.
12
+ *
13
+ * Returns `null` when the content is blank or not a raw gzip archive encoded as
14
+ * base64 — notably the legacy placeholder that contained a TypeScript
15
+ * `export const … = "";` stub, which must be treated as "no archive embedded"
16
+ * rather than decoded into garbage bytes.
17
+ */
18
+ export declare function decodeEmbeddedClientArchive(txt: string): Buffer | null;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Aggregates token-savings data for the Gain dashboard.
3
+ *
4
+ * Source:
5
+ * 1. Snapcompact: colocated with stats.db as snapcompact-savings.jsonl
6
+ *
7
+ * Missing files are treated as zero records — never an error.
8
+ */
9
+ import type { GainDashboardStats } from "./shared-types";
10
+ /**
11
+ * Collapse conventional worktree sub-paths to their logical project root.
12
+ *
13
+ * Rules are generic: omp internal wt paths are dropped; conventional worktree
14
+ * suffixes (`.wt/`, `-wt/`, `.worktrees/`, `-worktrees/`) are stripped. No
15
+ * author-specific IDE or tool paths are baked in.
16
+ *
17
+ * Returns null to drop temp/internal paths entirely.
18
+ */
19
+ export declare function normalizeProjectPath(p: string): string | null;
20
+ /**
21
+ * Given a raw set of paths, normalize worktree paths and remove sub-paths
22
+ * that are already covered by a shorter parent at depth ≥ 4.
23
+ * Returns a sorted, deduped list of meaningful project roots.
24
+ */
25
+ export declare function dedupeProjects(rawPaths: Set<string>): string[];
26
+ export declare function getGainDashboardStats(range?: string | null, project?: string | null): Promise<GainDashboardStats>;
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env bun
2
+ export { getDashboardStats, getToolDashboardStats, getTotalMessageCount, type SyncOptions, type SyncProgress, smokeTestSyncWorker, syncAllSessions, } from "./aggregator";
3
+ export { closeDb } from "./db";
4
+ export { getGainDashboardStats } from "./gain-aggregator";
5
+ export { startServer } from "./server";
6
+ export type { GainDashboardStats, GainSource, GainSourceTotals, GainTimeSeriesPoint, } from "./shared-types";
7
+ export type { AggregatedStats, DashboardStats, FolderStats, MessageStats, ModelPerformancePoint, ModelStats, ModelTimeSeriesPoint, TimeSeriesPoint, ToolDashboardStats, ToolModelStats, ToolTimeSeriesPoint, ToolUsageStats, } from "./types";
@@ -0,0 +1,53 @@
1
+ import type { AgentType, MessageStats, SessionEntry, ToolCallStats, ToolResultLink, UserMessageLink, UserMessageStats } from "./types";
2
+ /**
3
+ * Classify which agent produced a transcript from its path within the sessions
4
+ * directory. Layout: `<sessionsDir>/<project>/<file>.jsonl` is the `main`
5
+ * agent; subagent and advisor transcripts live nested one level deeper inside
6
+ * the session's artifacts dir (`<project>/<session>/<id>.jsonl`,
7
+ * `<project>/<session>/__advisor.jsonl`). Any advisor transcript
8
+ * (`__advisor.jsonl` or `__advisor.<slug>.jsonl`) — at any depth, including a
9
+ * subagent's own advisor — counts as `advisor`; every other nested transcript
10
+ * is a task `subagent`.
11
+ */
12
+ export declare function classifyAgentType(sessionPath: string): AgentType;
13
+ /**
14
+ * Parse a session file and extract all assistant message stats.
15
+ * Uses incremental reading with offset tracking.
16
+ *
17
+ * Service-tier carry-over: `currentServiceTier` is a session-scoped piece of
18
+ * state derived from `service_tier_change` entries that affects whether
19
+ * subsequent OpenAI assistant replies count as premium requests. Incremental
20
+ * syncs that resume past the most-recent tier change would otherwise lose
21
+ * that state and silently record `premiumRequests = 0` for priority traffic
22
+ * (the coding-agent stopped folding the tier into `usage.premiumRequests`
23
+ * after 13f59162e — the parser is now the sole source of truth). When
24
+ * `fromOffset > 0` we therefore scan the bytes preceding `fromOffset`
25
+ * for the latest service-tier value before parsing the unprocessed tail.
26
+ * The scan only keeps the current tier and does not materialize prefix
27
+ * entries, preserving offset-based memory behavior for large sessions.
28
+ */
29
+ export interface ParseSessionResult {
30
+ stats: MessageStats[];
31
+ userStats: UserMessageStats[];
32
+ userLinks: UserMessageLink[];
33
+ toolCalls: ToolCallStats[];
34
+ toolResults: ToolResultLink[];
35
+ newOffset: number;
36
+ }
37
+ export declare function parseSessionFile(sessionPath: string, fromOffset?: number): Promise<ParseSessionResult>;
38
+ /**
39
+ * List all session directories (folders).
40
+ */
41
+ export declare function listSessionFolders(): Promise<string[]>;
42
+ /**
43
+ * List all session files in a folder.
44
+ */
45
+ export declare function listSessionFiles(folderPath: string): Promise<string[]>;
46
+ /**
47
+ * List all session files across all folders.
48
+ */
49
+ export declare function listAllSessionFiles(): Promise<string[]>;
50
+ /**
51
+ * Find a specific entry in a session file.
52
+ */
53
+ export declare function getSessionEntry(sessionPath: string, entryId: string): Promise<SessionEntry | null>;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Start the HTTP server.
3
+ */
4
+ export declare function startServer(port?: number): Promise<{
5
+ port: number;
6
+ stop: () => void;
7
+ }>;