@tokentop/ttop 0.2.2
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.
- package/LICENSE +21 -0
- package/README.md +273 -0
- package/bin/ttop.js +49 -0
- package/package.json +68 -0
- package/src/agents/aggregator.ts +176 -0
- package/src/agents/costing.ts +74 -0
- package/src/agents/index.ts +12 -0
- package/src/agents/types.ts +71 -0
- package/src/cli.ts +167 -0
- package/src/config/schema.test.ts +166 -0
- package/src/config/schema.ts +165 -0
- package/src/demo/simulator.ts +745 -0
- package/src/plugins/agents/index.ts +2 -0
- package/src/plugins/auth-sources.ts +186 -0
- package/src/plugins/lifecycle.ts +177 -0
- package/src/plugins/loader.test.ts +195 -0
- package/src/plugins/loader.ts +282 -0
- package/src/plugins/notification-bus.ts +153 -0
- package/src/plugins/notifications/index.ts +2 -0
- package/src/plugins/notifications/terminal-bell.ts +66 -0
- package/src/plugins/notifications/visual-flash.ts +57 -0
- package/src/plugins/npm-installer.ts +164 -0
- package/src/plugins/plugin-context-factory.ts +67 -0
- package/src/plugins/plugin-host.ts +205 -0
- package/src/plugins/providers/anthropic.ts +325 -0
- package/src/plugins/providers/antigravity.ts +309 -0
- package/src/plugins/providers/codex.ts +250 -0
- package/src/plugins/providers/gemini.ts +391 -0
- package/src/plugins/providers/github-copilot.ts +194 -0
- package/src/plugins/providers/index.ts +12 -0
- package/src/plugins/providers/minimax.ts +223 -0
- package/src/plugins/providers/openai-api.ts +250 -0
- package/src/plugins/providers/opencode-zen.ts +125 -0
- package/src/plugins/providers/perplexity.ts +177 -0
- package/src/plugins/providers/zai.ts +225 -0
- package/src/plugins/registry.ts +251 -0
- package/src/plugins/sandbox-guard.test.ts +191 -0
- package/src/plugins/sandbox-guard.ts +155 -0
- package/src/plugins/sandbox.ts +138 -0
- package/src/plugins/themes/catppuccin-latte.ts +45 -0
- package/src/plugins/themes/catppuccin-mocha.ts +45 -0
- package/src/plugins/themes/claude-code.ts +45 -0
- package/src/plugins/themes/dracula.ts +45 -0
- package/src/plugins/themes/github-light.ts +45 -0
- package/src/plugins/themes/gruvbox-dark.ts +45 -0
- package/src/plugins/themes/gruvbox-light.ts +45 -0
- package/src/plugins/themes/index.ts +15 -0
- package/src/plugins/themes/kanagawa.ts +45 -0
- package/src/plugins/themes/nord.ts +45 -0
- package/src/plugins/themes/one-dark.ts +45 -0
- package/src/plugins/themes/opencode.ts +47 -0
- package/src/plugins/themes/rose-pine-dawn.ts +45 -0
- package/src/plugins/themes/rose-pine.ts +45 -0
- package/src/plugins/themes/solarized-light.ts +45 -0
- package/src/plugins/themes/tokyo-night.ts +49 -0
- package/src/plugins/types/agent.ts +108 -0
- package/src/plugins/types/base.ts +267 -0
- package/src/plugins/types/index.ts +19 -0
- package/src/plugins/types/notification.ts +51 -0
- package/src/plugins/types/provider.ts +233 -0
- package/src/plugins/types/theme.ts +70 -0
- package/src/plugins/update-checker.ts +177 -0
- package/src/pricing/estimator.test.ts +181 -0
- package/src/pricing/estimator.ts +80 -0
- package/src/pricing/fallback.test.ts +71 -0
- package/src/pricing/fallback.ts +62 -0
- package/src/pricing/index.ts +66 -0
- package/src/pricing/models-dev.ts +130 -0
- package/src/storage/database.ts +613 -0
- package/src/storage/db.ts +64 -0
- package/src/storage/index.ts +66 -0
- package/src/storage/migrations/index.ts +189 -0
- package/src/storage/paths.ts +24 -0
- package/src/storage/repos/agentSessions.ts +210 -0
- package/src/storage/repos/providerSnapshots.ts +116 -0
- package/src/storage/repos/usageEvents.ts +334 -0
- package/src/storage/types.ts +288 -0
- package/src/tui/App.tsx +381 -0
- package/src/tui/components/CommandPalette.tsx +166 -0
- package/src/tui/components/DebugConsole.tsx +161 -0
- package/src/tui/components/DebugPanel.tsx +660 -0
- package/src/tui/components/FooterHints.tsx +22 -0
- package/src/tui/components/GhostProviderCard.tsx +45 -0
- package/src/tui/components/Header.tsx +125 -0
- package/src/tui/components/HelpOverlay.tsx +45 -0
- package/src/tui/components/InlineGauge.tsx +34 -0
- package/src/tui/components/InlineSparkline.tsx +78 -0
- package/src/tui/components/KpiStrip.tsx +246 -0
- package/src/tui/components/LimitGauge.tsx +186 -0
- package/src/tui/components/ModalBackdrop.tsx +42 -0
- package/src/tui/components/ProviderAggregateStrip.tsx +200 -0
- package/src/tui/components/ProviderCard.tsx +309 -0
- package/src/tui/components/ProviderDetailPanel.tsx +237 -0
- package/src/tui/components/ProviderLimitsPanel.tsx +308 -0
- package/src/tui/components/ProvidersList.tsx +380 -0
- package/src/tui/components/SessionDetailsDrawer.tsx +317 -0
- package/src/tui/components/SessionsTable.tsx +401 -0
- package/src/tui/components/SettingsModal.tsx +587 -0
- package/src/tui/components/SidebarBreakdown.tsx +87 -0
- package/src/tui/components/Skeleton.tsx +102 -0
- package/src/tui/components/SmartSidebar.tsx +644 -0
- package/src/tui/components/Sparkline.tsx +180 -0
- package/src/tui/components/Spinner.tsx +36 -0
- package/src/tui/components/StatusBar.tsx +96 -0
- package/src/tui/components/ThemePicker.tsx +234 -0
- package/src/tui/components/Toast.tsx +61 -0
- package/src/tui/components/UsageGauge.tsx +80 -0
- package/src/tui/components/index.ts +5 -0
- package/src/tui/contexts/AgentSessionContext.tsx +307 -0
- package/src/tui/contexts/ConfigContext.tsx +141 -0
- package/src/tui/contexts/DashboardRuntimeContext.tsx +102 -0
- package/src/tui/contexts/DemoModeContext.tsx +60 -0
- package/src/tui/contexts/DrawerContext.tsx +42 -0
- package/src/tui/contexts/InputContext.tsx +30 -0
- package/src/tui/contexts/LogContext.tsx +150 -0
- package/src/tui/contexts/PluginContext.tsx +424 -0
- package/src/tui/contexts/RealTimeActivityContext.tsx +119 -0
- package/src/tui/contexts/StorageContext.tsx +233 -0
- package/src/tui/contexts/ThemeContext.tsx +95 -0
- package/src/tui/contexts/TimeWindowContext.tsx +158 -0
- package/src/tui/contexts/ToastContext.tsx +40 -0
- package/src/tui/contexts/index.ts +5 -0
- package/src/tui/createApp.tsx +47 -0
- package/src/tui/debug/captureFrame.ts +262 -0
- package/src/tui/debug/snapshot.tsx +644 -0
- package/src/tui/driver/assertions.ts +279 -0
- package/src/tui/driver/cli.ts +717 -0
- package/src/tui/driver/coverage.ts +204 -0
- package/src/tui/driver/demo-snapshot.ts +66 -0
- package/src/tui/driver/diff.ts +166 -0
- package/src/tui/driver/driver.ts +270 -0
- package/src/tui/driver/index.ts +53 -0
- package/src/tui/driver/recorder.ts +303 -0
- package/src/tui/driver/test-workflow.ts +115 -0
- package/src/tui/driver/test.ts +47 -0
- package/src/tui/hooks/index.ts +20 -0
- package/src/tui/hooks/useAnimatedValue.ts +113 -0
- package/src/tui/hooks/useDashboardKeyboard.ts +341 -0
- package/src/tui/hooks/useDashboardState.ts +116 -0
- package/src/tui/hooks/useEmaActivity.ts +223 -0
- package/src/tui/hooks/useEntranceAnimation.ts +62 -0
- package/src/tui/hooks/useExitAnimation.ts +137 -0
- package/src/tui/hooks/usePulse.ts +138 -0
- package/src/tui/hooks/useSafeRenderer.ts +24 -0
- package/src/tui/hooks/useValueFlash.ts +111 -0
- package/src/tui/index.tsx +50 -0
- package/src/tui/utils/providerColor.ts +32 -0
- package/src/tui/views/Dashboard.tsx +340 -0
- package/src/tui/views/HistoricalTrendsView.tsx +1431 -0
- package/src/tui/views/ProjectsView.tsx +1277 -0
- package/src/tui/views/RealTimeDashboard.tsx +393 -0
- package/src/tui/views/SettingsView.tsx +465 -0
- package/src/tui/views/index.ts +2 -0
- package/src/types/global.d.ts +5 -0
- package/src/types/opentui-test-utils.d.ts +18 -0
- package/src/utils/clipboard.ts +118 -0
- package/src/utils/currency.test.ts +106 -0
- package/src/utils/currency.ts +79 -0
- package/src/version.ts +1 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export { PATHS } from './paths.ts';
|
|
2
|
+
|
|
3
|
+
export {
|
|
4
|
+
initDatabase,
|
|
5
|
+
getDatabase,
|
|
6
|
+
closeDatabase,
|
|
7
|
+
isDatabaseInitialized,
|
|
8
|
+
getAppRunId,
|
|
9
|
+
} from './db.ts';
|
|
10
|
+
|
|
11
|
+
export {
|
|
12
|
+
insertProviderSnapshot,
|
|
13
|
+
insertProviderSnapshotBatch,
|
|
14
|
+
queryProviderSnapshots,
|
|
15
|
+
getLatestProviderSnapshot,
|
|
16
|
+
} from './repos/providerSnapshots.ts';
|
|
17
|
+
|
|
18
|
+
export {
|
|
19
|
+
insertUsageEvent,
|
|
20
|
+
insertUsageEventBatch,
|
|
21
|
+
queryUsageTimeSeries,
|
|
22
|
+
queryProviderDailyCosts,
|
|
23
|
+
queryModelDailyCosts,
|
|
24
|
+
queryProjectDailyCosts,
|
|
25
|
+
calculateBurnRate,
|
|
26
|
+
getTotalUsageInWindow,
|
|
27
|
+
getSessionActivityTimeline,
|
|
28
|
+
} from './repos/usageEvents.ts';
|
|
29
|
+
|
|
30
|
+
export type { SessionActivityPoint, ProviderDailyCost, ModelDailyCost, ProjectDailyCost } from './repos/usageEvents.ts';
|
|
31
|
+
|
|
32
|
+
export {
|
|
33
|
+
upsertAgentSession,
|
|
34
|
+
insertAgentSessionSnapshot,
|
|
35
|
+
getAgentSession,
|
|
36
|
+
getRecentSessions,
|
|
37
|
+
getSessionsByProject,
|
|
38
|
+
getLatestStreamTotalsForAllSessions,
|
|
39
|
+
} from './repos/agentSessions.ts';
|
|
40
|
+
|
|
41
|
+
export type { LatestStreamTotals } from './repos/agentSessions.ts';
|
|
42
|
+
|
|
43
|
+
export type {
|
|
44
|
+
UsageEventSource,
|
|
45
|
+
SessionStatus,
|
|
46
|
+
CostSource,
|
|
47
|
+
PricingSource,
|
|
48
|
+
AppRunRow,
|
|
49
|
+
ProviderSnapshotRow,
|
|
50
|
+
ProviderSnapshotInsert,
|
|
51
|
+
AgentSessionDim,
|
|
52
|
+
AgentSessionUpsert,
|
|
53
|
+
AgentSessionSnapshotRow,
|
|
54
|
+
AgentSessionSnapshotInsert,
|
|
55
|
+
AgentSessionStreamSnapshotRow,
|
|
56
|
+
UsageEventRow,
|
|
57
|
+
UsageEventInsert,
|
|
58
|
+
HourlyAggregateRow,
|
|
59
|
+
DailyAggregateRow,
|
|
60
|
+
TimeSeriesPoint,
|
|
61
|
+
TimeSeriesFilters,
|
|
62
|
+
UsageQueryOptions,
|
|
63
|
+
StreamTotals,
|
|
64
|
+
} from './types.ts';
|
|
65
|
+
|
|
66
|
+
export { computeStreamDelta } from './types.ts';
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import type { Database } from 'bun:sqlite';
|
|
2
|
+
|
|
3
|
+
export interface Migration {
|
|
4
|
+
version: number;
|
|
5
|
+
up: (db: Database) => void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const migrations: Migration[] = [
|
|
9
|
+
{
|
|
10
|
+
version: 1,
|
|
11
|
+
up: (db: Database) => {
|
|
12
|
+
db.exec(`
|
|
13
|
+
CREATE TABLE IF NOT EXISTS meta (
|
|
14
|
+
key TEXT PRIMARY KEY,
|
|
15
|
+
value TEXT NOT NULL
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
CREATE TABLE IF NOT EXISTS app_runs (
|
|
19
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
20
|
+
started_at INTEGER NOT NULL,
|
|
21
|
+
ended_at INTEGER,
|
|
22
|
+
app_version TEXT,
|
|
23
|
+
refresh_interval_ms INTEGER,
|
|
24
|
+
pid INTEGER,
|
|
25
|
+
hostname TEXT
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
CREATE TABLE IF NOT EXISTS provider_snapshots (
|
|
29
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
30
|
+
timestamp INTEGER NOT NULL,
|
|
31
|
+
provider TEXT NOT NULL,
|
|
32
|
+
used_percent REAL,
|
|
33
|
+
limit_reached INTEGER NOT NULL DEFAULT 0,
|
|
34
|
+
resets_at INTEGER,
|
|
35
|
+
tokens_input INTEGER,
|
|
36
|
+
tokens_output INTEGER,
|
|
37
|
+
cost_usd REAL,
|
|
38
|
+
cost_source TEXT,
|
|
39
|
+
raw_json TEXT,
|
|
40
|
+
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
CREATE INDEX IF NOT EXISTS idx_provider_snapshots_provider_ts
|
|
44
|
+
ON provider_snapshots(provider, timestamp);
|
|
45
|
+
|
|
46
|
+
CREATE INDEX IF NOT EXISTS idx_provider_snapshots_ts
|
|
47
|
+
ON provider_snapshots(timestamp);
|
|
48
|
+
|
|
49
|
+
CREATE TABLE IF NOT EXISTS agent_sessions (
|
|
50
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
51
|
+
agent_id TEXT NOT NULL,
|
|
52
|
+
session_id TEXT NOT NULL,
|
|
53
|
+
project_path TEXT,
|
|
54
|
+
started_at INTEGER,
|
|
55
|
+
first_seen_at INTEGER NOT NULL,
|
|
56
|
+
last_seen_at INTEGER NOT NULL,
|
|
57
|
+
UNIQUE(agent_id, session_id)
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
CREATE INDEX IF NOT EXISTS idx_agent_sessions_last_seen
|
|
61
|
+
ON agent_sessions(last_seen_at);
|
|
62
|
+
|
|
63
|
+
CREATE INDEX IF NOT EXISTS idx_agent_sessions_project
|
|
64
|
+
ON agent_sessions(project_path);
|
|
65
|
+
|
|
66
|
+
CREATE TABLE IF NOT EXISTS agent_session_snapshots (
|
|
67
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
68
|
+
timestamp INTEGER NOT NULL,
|
|
69
|
+
agent_session_id INTEGER NOT NULL REFERENCES agent_sessions(id) ON DELETE CASCADE,
|
|
70
|
+
last_activity_at INTEGER,
|
|
71
|
+
status TEXT,
|
|
72
|
+
total_input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
73
|
+
total_output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
74
|
+
total_cache_read_tokens INTEGER NOT NULL DEFAULT 0,
|
|
75
|
+
total_cache_write_tokens INTEGER NOT NULL DEFAULT 0,
|
|
76
|
+
total_cost_usd REAL NOT NULL DEFAULT 0,
|
|
77
|
+
request_count INTEGER NOT NULL DEFAULT 0
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
CREATE INDEX IF NOT EXISTS idx_agent_session_snapshots_session_ts
|
|
81
|
+
ON agent_session_snapshots(agent_session_id, timestamp);
|
|
82
|
+
|
|
83
|
+
CREATE INDEX IF NOT EXISTS idx_agent_session_snapshots_ts
|
|
84
|
+
ON agent_session_snapshots(timestamp);
|
|
85
|
+
|
|
86
|
+
CREATE TABLE IF NOT EXISTS agent_session_stream_snapshots (
|
|
87
|
+
agent_session_snapshot_id INTEGER NOT NULL REFERENCES agent_session_snapshots(id) ON DELETE CASCADE,
|
|
88
|
+
provider TEXT NOT NULL,
|
|
89
|
+
model TEXT NOT NULL,
|
|
90
|
+
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
91
|
+
output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
92
|
+
cache_read_tokens INTEGER NOT NULL DEFAULT 0,
|
|
93
|
+
cache_write_tokens INTEGER NOT NULL DEFAULT 0,
|
|
94
|
+
cost_usd REAL NOT NULL DEFAULT 0,
|
|
95
|
+
request_count INTEGER NOT NULL DEFAULT 0,
|
|
96
|
+
pricing_source TEXT,
|
|
97
|
+
PRIMARY KEY (agent_session_snapshot_id, provider, model)
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
CREATE INDEX IF NOT EXISTS idx_agent_stream_provider_model
|
|
101
|
+
ON agent_session_stream_snapshots(provider, model);
|
|
102
|
+
|
|
103
|
+
CREATE TABLE IF NOT EXISTS usage_events (
|
|
104
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
105
|
+
timestamp INTEGER NOT NULL,
|
|
106
|
+
source TEXT NOT NULL,
|
|
107
|
+
provider TEXT,
|
|
108
|
+
model TEXT,
|
|
109
|
+
agent_id TEXT,
|
|
110
|
+
session_id TEXT,
|
|
111
|
+
project_path TEXT,
|
|
112
|
+
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
113
|
+
output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
114
|
+
cache_read_tokens INTEGER NOT NULL DEFAULT 0,
|
|
115
|
+
cache_write_tokens INTEGER NOT NULL DEFAULT 0,
|
|
116
|
+
cost_usd REAL NOT NULL DEFAULT 0,
|
|
117
|
+
request_count INTEGER NOT NULL DEFAULT 0,
|
|
118
|
+
pricing_source TEXT,
|
|
119
|
+
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
CREATE INDEX IF NOT EXISTS idx_usage_events_ts ON usage_events(timestamp);
|
|
123
|
+
CREATE INDEX IF NOT EXISTS idx_usage_events_provider_ts ON usage_events(provider, timestamp);
|
|
124
|
+
CREATE INDEX IF NOT EXISTS idx_usage_events_provider_model_ts ON usage_events(provider, model, timestamp);
|
|
125
|
+
CREATE INDEX IF NOT EXISTS idx_usage_events_agent_ts ON usage_events(agent_id, timestamp);
|
|
126
|
+
CREATE INDEX IF NOT EXISTS idx_usage_events_session_ts ON usage_events(session_id, timestamp);
|
|
127
|
+
CREATE INDEX IF NOT EXISTS idx_usage_events_project_ts ON usage_events(project_path, timestamp);
|
|
128
|
+
|
|
129
|
+
CREATE TABLE IF NOT EXISTS hourly_aggregates (
|
|
130
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
131
|
+
bucket_start INTEGER NOT NULL,
|
|
132
|
+
provider TEXT NOT NULL DEFAULT '',
|
|
133
|
+
model TEXT NOT NULL DEFAULT '',
|
|
134
|
+
agent_id TEXT NOT NULL DEFAULT '',
|
|
135
|
+
project_path TEXT NOT NULL DEFAULT '',
|
|
136
|
+
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
137
|
+
output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
138
|
+
cache_read_tokens INTEGER NOT NULL DEFAULT 0,
|
|
139
|
+
cache_write_tokens INTEGER NOT NULL DEFAULT 0,
|
|
140
|
+
cost_usd REAL NOT NULL DEFAULT 0,
|
|
141
|
+
request_count INTEGER NOT NULL DEFAULT 0,
|
|
142
|
+
UNIQUE (bucket_start, provider, model, agent_id, project_path)
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
CREATE INDEX IF NOT EXISTS idx_hourly_aggregates_bucket ON hourly_aggregates(bucket_start);
|
|
146
|
+
|
|
147
|
+
CREATE TABLE IF NOT EXISTS daily_aggregates (
|
|
148
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
149
|
+
date TEXT NOT NULL,
|
|
150
|
+
provider TEXT NOT NULL DEFAULT '',
|
|
151
|
+
model TEXT NOT NULL DEFAULT '',
|
|
152
|
+
agent_id TEXT NOT NULL DEFAULT '',
|
|
153
|
+
project_path TEXT NOT NULL DEFAULT '',
|
|
154
|
+
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
155
|
+
output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
156
|
+
cache_read_tokens INTEGER NOT NULL DEFAULT 0,
|
|
157
|
+
cache_write_tokens INTEGER NOT NULL DEFAULT 0,
|
|
158
|
+
cost_usd REAL NOT NULL DEFAULT 0,
|
|
159
|
+
request_count INTEGER NOT NULL DEFAULT 0,
|
|
160
|
+
UNIQUE (date, provider, model, agent_id, project_path)
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
CREATE INDEX IF NOT EXISTS idx_daily_aggregates_date ON daily_aggregates(date);
|
|
164
|
+
`);
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
];
|
|
168
|
+
|
|
169
|
+
export function getCurrentVersion(db: Database): number {
|
|
170
|
+
const result = db.prepare('PRAGMA user_version').get() as { user_version: number } | null;
|
|
171
|
+
return result?.user_version ?? 0;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function setVersion(db: Database, version: number): void {
|
|
175
|
+
db.exec(`PRAGMA user_version = ${version}`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function applyMigrations(db: Database): void {
|
|
179
|
+
const currentVersion = getCurrentVersion(db);
|
|
180
|
+
|
|
181
|
+
for (const migration of migrations) {
|
|
182
|
+
if (migration.version > currentVersion) {
|
|
183
|
+
db.transaction(() => {
|
|
184
|
+
migration.up(db);
|
|
185
|
+
setVersion(db, migration.version);
|
|
186
|
+
})();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import * as os from 'os';
|
|
3
|
+
|
|
4
|
+
const HOME = os.homedir();
|
|
5
|
+
|
|
6
|
+
export const PATHS = {
|
|
7
|
+
config: {
|
|
8
|
+
dir: path.join(HOME, '.config/tokentop'),
|
|
9
|
+
file: path.join(HOME, '.config/tokentop/config.json'),
|
|
10
|
+
fileJsonc: path.join(HOME, '.config/tokentop/config.jsonc'),
|
|
11
|
+
plugins: path.join(HOME, '.config/tokentop/plugins'),
|
|
12
|
+
},
|
|
13
|
+
data: {
|
|
14
|
+
dir: path.join(HOME, '.local/share/tokentop'),
|
|
15
|
+
database: path.join(HOME, '.local/share/tokentop/usage.db'),
|
|
16
|
+
sessions: path.join(HOME, '.local/share/tokentop/sessions'),
|
|
17
|
+
cache: path.join(HOME, '.local/share/tokentop/cache'),
|
|
18
|
+
logs: path.join(HOME, '.local/share/tokentop/logs'),
|
|
19
|
+
},
|
|
20
|
+
cache: {
|
|
21
|
+
dir: path.join(HOME, '.cache/tokentop'),
|
|
22
|
+
nodeModules: path.join(HOME, '.cache/tokentop/node_modules'),
|
|
23
|
+
},
|
|
24
|
+
} as const;
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { getDatabase } from '../db.ts';
|
|
2
|
+
import type {
|
|
3
|
+
AgentSessionDim,
|
|
4
|
+
AgentSessionUpsert,
|
|
5
|
+
AgentSessionSnapshotInsert,
|
|
6
|
+
AgentSessionStreamSnapshotRow,
|
|
7
|
+
} from '../types.ts';
|
|
8
|
+
|
|
9
|
+
const UPSERT_SESSION_SQL = `
|
|
10
|
+
INSERT INTO agent_sessions (agent_id, session_id, project_path, started_at, first_seen_at, last_seen_at)
|
|
11
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
12
|
+
ON CONFLICT(agent_id, session_id) DO UPDATE SET
|
|
13
|
+
project_path = COALESCE(excluded.project_path, agent_sessions.project_path),
|
|
14
|
+
started_at = COALESCE(excluded.started_at, agent_sessions.started_at),
|
|
15
|
+
last_seen_at = excluded.last_seen_at
|
|
16
|
+
RETURNING id
|
|
17
|
+
`;
|
|
18
|
+
|
|
19
|
+
const INSERT_SNAPSHOT_SQL = `
|
|
20
|
+
INSERT INTO agent_session_snapshots (
|
|
21
|
+
timestamp, agent_session_id, last_activity_at, status,
|
|
22
|
+
total_input_tokens, total_output_tokens, total_cache_read_tokens, total_cache_write_tokens,
|
|
23
|
+
total_cost_usd, request_count
|
|
24
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
25
|
+
`;
|
|
26
|
+
|
|
27
|
+
const INSERT_STREAM_SQL = `
|
|
28
|
+
INSERT INTO agent_session_stream_snapshots (
|
|
29
|
+
agent_session_snapshot_id, provider, model,
|
|
30
|
+
input_tokens, output_tokens, cache_read_tokens, cache_write_tokens,
|
|
31
|
+
cost_usd, request_count, pricing_source
|
|
32
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
33
|
+
`;
|
|
34
|
+
|
|
35
|
+
export function upsertAgentSession(session: AgentSessionUpsert): number {
|
|
36
|
+
const db = getDatabase();
|
|
37
|
+
const now = Date.now();
|
|
38
|
+
|
|
39
|
+
const row = db.prepare(UPSERT_SESSION_SQL).get(
|
|
40
|
+
session.agentId,
|
|
41
|
+
session.sessionId,
|
|
42
|
+
session.projectPath ?? null,
|
|
43
|
+
session.startedAt ?? null,
|
|
44
|
+
session.firstSeenAt ?? now,
|
|
45
|
+
session.lastSeenAt
|
|
46
|
+
) as { id: number };
|
|
47
|
+
|
|
48
|
+
return row.id;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function insertAgentSessionSnapshot(
|
|
52
|
+
snapshot: AgentSessionSnapshotInsert,
|
|
53
|
+
streams: Omit<AgentSessionStreamSnapshotRow, 'agentSessionSnapshotId'>[]
|
|
54
|
+
): number {
|
|
55
|
+
const db = getDatabase();
|
|
56
|
+
|
|
57
|
+
const insertSnapshot = db.prepare(INSERT_SNAPSHOT_SQL);
|
|
58
|
+
const insertStream = db.prepare(INSERT_STREAM_SQL);
|
|
59
|
+
|
|
60
|
+
let snapshotId = 0;
|
|
61
|
+
|
|
62
|
+
db.transaction(() => {
|
|
63
|
+
const result = insertSnapshot.run(
|
|
64
|
+
snapshot.timestamp,
|
|
65
|
+
snapshot.agentSessionId,
|
|
66
|
+
snapshot.lastActivityAt ?? null,
|
|
67
|
+
snapshot.status ?? null,
|
|
68
|
+
snapshot.totalInputTokens,
|
|
69
|
+
snapshot.totalOutputTokens,
|
|
70
|
+
snapshot.totalCacheReadTokens,
|
|
71
|
+
snapshot.totalCacheWriteTokens,
|
|
72
|
+
snapshot.totalCostUsd,
|
|
73
|
+
snapshot.requestCount
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
snapshotId = Number(result.lastInsertRowid);
|
|
77
|
+
|
|
78
|
+
for (const stream of streams) {
|
|
79
|
+
insertStream.run(
|
|
80
|
+
snapshotId,
|
|
81
|
+
stream.provider,
|
|
82
|
+
stream.model,
|
|
83
|
+
stream.inputTokens,
|
|
84
|
+
stream.outputTokens,
|
|
85
|
+
stream.cacheReadTokens,
|
|
86
|
+
stream.cacheWriteTokens,
|
|
87
|
+
stream.costUsd,
|
|
88
|
+
stream.requestCount,
|
|
89
|
+
stream.pricingSource ?? null
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
})();
|
|
93
|
+
|
|
94
|
+
return snapshotId;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function getAgentSession(agentId: string, sessionId: string): AgentSessionDim | null {
|
|
98
|
+
const db = getDatabase();
|
|
99
|
+
|
|
100
|
+
const row = db.prepare(`
|
|
101
|
+
SELECT * FROM agent_sessions WHERE agent_id = ? AND session_id = ?
|
|
102
|
+
`).get(agentId, sessionId) as DbSessionRow | null;
|
|
103
|
+
|
|
104
|
+
return row ? mapSessionRow(row) : null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function getRecentSessions(limit = 50): AgentSessionDim[] {
|
|
108
|
+
const db = getDatabase();
|
|
109
|
+
|
|
110
|
+
const rows = db.prepare(`
|
|
111
|
+
SELECT * FROM agent_sessions
|
|
112
|
+
ORDER BY last_seen_at DESC
|
|
113
|
+
LIMIT ?
|
|
114
|
+
`).all(limit) as DbSessionRow[];
|
|
115
|
+
|
|
116
|
+
return rows.map(mapSessionRow);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function getSessionsByProject(projectPath: string, limit = 50): AgentSessionDim[] {
|
|
120
|
+
const db = getDatabase();
|
|
121
|
+
|
|
122
|
+
const rows = db.prepare(`
|
|
123
|
+
SELECT * FROM agent_sessions
|
|
124
|
+
WHERE project_path = ?
|
|
125
|
+
ORDER BY last_seen_at DESC
|
|
126
|
+
LIMIT ?
|
|
127
|
+
`).all(projectPath, limit) as DbSessionRow[];
|
|
128
|
+
|
|
129
|
+
return rows.map(mapSessionRow);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
interface DbSessionRow {
|
|
133
|
+
id: number;
|
|
134
|
+
agent_id: string;
|
|
135
|
+
session_id: string;
|
|
136
|
+
project_path: string | null;
|
|
137
|
+
started_at: number | null;
|
|
138
|
+
first_seen_at: number;
|
|
139
|
+
last_seen_at: number;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function mapSessionRow(row: DbSessionRow): AgentSessionDim {
|
|
143
|
+
return {
|
|
144
|
+
id: row.id,
|
|
145
|
+
agentId: row.agent_id,
|
|
146
|
+
sessionId: row.session_id,
|
|
147
|
+
projectPath: row.project_path,
|
|
148
|
+
startedAt: row.started_at,
|
|
149
|
+
firstSeenAt: row.first_seen_at,
|
|
150
|
+
lastSeenAt: row.last_seen_at,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface LatestStreamTotals {
|
|
155
|
+
agentId: string;
|
|
156
|
+
sessionId: string;
|
|
157
|
+
provider: string;
|
|
158
|
+
model: string;
|
|
159
|
+
inputTokens: number;
|
|
160
|
+
outputTokens: number;
|
|
161
|
+
cacheReadTokens: number;
|
|
162
|
+
cacheWriteTokens: number;
|
|
163
|
+
costUsd: number;
|
|
164
|
+
requestCount: number;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function getLatestStreamTotalsForAllSessions(): LatestStreamTotals[] {
|
|
168
|
+
const db = getDatabase();
|
|
169
|
+
|
|
170
|
+
const rows = db.prepare(`
|
|
171
|
+
SELECT
|
|
172
|
+
s.agent_id, s.session_id,
|
|
173
|
+
ss.provider, ss.model,
|
|
174
|
+
ss.input_tokens, ss.output_tokens,
|
|
175
|
+
ss.cache_read_tokens, ss.cache_write_tokens,
|
|
176
|
+
ss.cost_usd, ss.request_count
|
|
177
|
+
FROM agent_session_stream_snapshots ss
|
|
178
|
+
JOIN agent_session_snapshots snap ON snap.id = ss.agent_session_snapshot_id
|
|
179
|
+
JOIN agent_sessions s ON s.id = snap.agent_session_id
|
|
180
|
+
WHERE snap.id IN (
|
|
181
|
+
SELECT MAX(snap2.id)
|
|
182
|
+
FROM agent_session_snapshots snap2
|
|
183
|
+
GROUP BY snap2.agent_session_id
|
|
184
|
+
)
|
|
185
|
+
`).all() as Array<{
|
|
186
|
+
agent_id: string;
|
|
187
|
+
session_id: string;
|
|
188
|
+
provider: string;
|
|
189
|
+
model: string;
|
|
190
|
+
input_tokens: number;
|
|
191
|
+
output_tokens: number;
|
|
192
|
+
cache_read_tokens: number;
|
|
193
|
+
cache_write_tokens: number;
|
|
194
|
+
cost_usd: number;
|
|
195
|
+
request_count: number;
|
|
196
|
+
}>;
|
|
197
|
+
|
|
198
|
+
return rows.map(r => ({
|
|
199
|
+
agentId: r.agent_id,
|
|
200
|
+
sessionId: r.session_id,
|
|
201
|
+
provider: r.provider,
|
|
202
|
+
model: r.model,
|
|
203
|
+
inputTokens: r.input_tokens,
|
|
204
|
+
outputTokens: r.output_tokens,
|
|
205
|
+
cacheReadTokens: r.cache_read_tokens,
|
|
206
|
+
cacheWriteTokens: r.cache_write_tokens,
|
|
207
|
+
costUsd: r.cost_usd,
|
|
208
|
+
requestCount: r.request_count,
|
|
209
|
+
}));
|
|
210
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { getDatabase } from '../db.ts';
|
|
2
|
+
import type { ProviderSnapshotInsert, ProviderSnapshotRow, CostSource } from '../types.ts';
|
|
3
|
+
|
|
4
|
+
const INSERT_SQL = `
|
|
5
|
+
INSERT INTO provider_snapshots (
|
|
6
|
+
timestamp, provider, used_percent, limit_reached, resets_at,
|
|
7
|
+
tokens_input, tokens_output, cost_usd, cost_source, raw_json
|
|
8
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
9
|
+
`;
|
|
10
|
+
|
|
11
|
+
const QUERY_BY_PROVIDER_SQL = `
|
|
12
|
+
SELECT * FROM provider_snapshots
|
|
13
|
+
WHERE provider = ? AND timestamp >= ? AND timestamp <= ?
|
|
14
|
+
ORDER BY timestamp DESC
|
|
15
|
+
LIMIT ?
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
const QUERY_LATEST_SQL = `
|
|
19
|
+
SELECT * FROM provider_snapshots
|
|
20
|
+
WHERE provider = ?
|
|
21
|
+
ORDER BY timestamp DESC
|
|
22
|
+
LIMIT 1
|
|
23
|
+
`;
|
|
24
|
+
|
|
25
|
+
export function insertProviderSnapshot(snapshot: ProviderSnapshotInsert): number {
|
|
26
|
+
const db = getDatabase();
|
|
27
|
+
const stmt = db.prepare(INSERT_SQL);
|
|
28
|
+
|
|
29
|
+
const result = stmt.run(
|
|
30
|
+
snapshot.timestamp,
|
|
31
|
+
snapshot.provider,
|
|
32
|
+
snapshot.usedPercent ?? null,
|
|
33
|
+
snapshot.limitReached ? 1 : 0,
|
|
34
|
+
snapshot.resetsAt ?? null,
|
|
35
|
+
snapshot.tokensInput ?? null,
|
|
36
|
+
snapshot.tokensOutput ?? null,
|
|
37
|
+
snapshot.costUsd ?? null,
|
|
38
|
+
snapshot.costSource ?? null,
|
|
39
|
+
snapshot.rawJson ?? null
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
return Number(result.lastInsertRowid);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function insertProviderSnapshotBatch(snapshots: ProviderSnapshotInsert[]): void {
|
|
46
|
+
if (snapshots.length === 0) return;
|
|
47
|
+
|
|
48
|
+
const db = getDatabase();
|
|
49
|
+
const stmt = db.prepare(INSERT_SQL);
|
|
50
|
+
|
|
51
|
+
const insertMany = db.transaction((items: ProviderSnapshotInsert[]) => {
|
|
52
|
+
for (const s of items) {
|
|
53
|
+
stmt.run(
|
|
54
|
+
s.timestamp,
|
|
55
|
+
s.provider,
|
|
56
|
+
s.usedPercent ?? null,
|
|
57
|
+
s.limitReached ? 1 : 0,
|
|
58
|
+
s.resetsAt ?? null,
|
|
59
|
+
s.tokensInput ?? null,
|
|
60
|
+
s.tokensOutput ?? null,
|
|
61
|
+
s.costUsd ?? null,
|
|
62
|
+
s.costSource ?? null,
|
|
63
|
+
s.rawJson ?? null
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
insertMany(snapshots);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function queryProviderSnapshots(
|
|
72
|
+
provider: string,
|
|
73
|
+
startMs: number,
|
|
74
|
+
endMs: number,
|
|
75
|
+
limit = 100
|
|
76
|
+
): ProviderSnapshotRow[] {
|
|
77
|
+
const db = getDatabase();
|
|
78
|
+
const rows = db.prepare(QUERY_BY_PROVIDER_SQL).all(provider, startMs, endMs, limit) as DbRow[];
|
|
79
|
+
return rows.map(mapRow);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function getLatestProviderSnapshot(provider: string): ProviderSnapshotRow | null {
|
|
83
|
+
const db = getDatabase();
|
|
84
|
+
const row = db.prepare(QUERY_LATEST_SQL).get(provider) as DbRow | null;
|
|
85
|
+
return row ? mapRow(row) : null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
interface DbRow {
|
|
89
|
+
id: number;
|
|
90
|
+
timestamp: number;
|
|
91
|
+
provider: string;
|
|
92
|
+
used_percent: number | null;
|
|
93
|
+
limit_reached: number;
|
|
94
|
+
resets_at: number | null;
|
|
95
|
+
tokens_input: number | null;
|
|
96
|
+
tokens_output: number | null;
|
|
97
|
+
cost_usd: number | null;
|
|
98
|
+
cost_source: string | null;
|
|
99
|
+
raw_json: string | null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function mapRow(row: DbRow): ProviderSnapshotRow {
|
|
103
|
+
return {
|
|
104
|
+
id: row.id,
|
|
105
|
+
timestamp: row.timestamp,
|
|
106
|
+
provider: row.provider,
|
|
107
|
+
usedPercent: row.used_percent,
|
|
108
|
+
limitReached: row.limit_reached === 1,
|
|
109
|
+
resetsAt: row.resets_at,
|
|
110
|
+
tokensInput: row.tokens_input,
|
|
111
|
+
tokensOutput: row.tokens_output,
|
|
112
|
+
costUsd: row.cost_usd,
|
|
113
|
+
costSource: row.cost_source as CostSource | null,
|
|
114
|
+
rawJson: row.raw_json,
|
|
115
|
+
};
|
|
116
|
+
}
|