claudeck 1.0.0
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 +233 -0
- package/cli.js +2 -0
- package/config/agent-chains.json +16 -0
- package/config/agent-dags.json +16 -0
- package/config/agents.json +46 -0
- package/config/bot-prompt.json +3 -0
- package/config/folders.json +66 -0
- package/config/prompts.json +92 -0
- package/config/repos.json +86 -0
- package/config/telegram-config.json +17 -0
- package/config/workflows.json +90 -0
- package/db.js +1198 -0
- package/package.json +55 -0
- package/plugins/claude-editor/client.css +171 -0
- package/plugins/claude-editor/client.js +183 -0
- package/plugins/event-stream/client.css +207 -0
- package/plugins/event-stream/client.js +271 -0
- package/plugins/linear/client.css +345 -0
- package/plugins/linear/client.js +380 -0
- package/plugins/linear/config.json +5 -0
- package/plugins/linear/server.js +312 -0
- package/plugins/repos/client.css +549 -0
- package/plugins/repos/client.js +663 -0
- package/plugins/repos/server.js +232 -0
- package/plugins/sudoku/client.css +196 -0
- package/plugins/sudoku/client.js +329 -0
- package/plugins/tasks/client.css +414 -0
- package/plugins/tasks/client.js +394 -0
- package/plugins/tasks/server.js +116 -0
- package/plugins/tic-tac-toe/client.css +167 -0
- package/plugins/tic-tac-toe/client.js +241 -0
- package/public/css/core/components.css +232 -0
- package/public/css/core/layout.css +330 -0
- package/public/css/core/print.css +18 -0
- package/public/css/core/reset.css +36 -0
- package/public/css/core/responsive.css +378 -0
- package/public/css/core/theme.css +116 -0
- package/public/css/core/variables.css +93 -0
- package/public/css/features/agent-monitor.css +297 -0
- package/public/css/features/agent-sidebar.css +525 -0
- package/public/css/features/agents.css +996 -0
- package/public/css/features/analytics.css +181 -0
- package/public/css/features/background-sessions.css +321 -0
- package/public/css/features/cost-dashboard.css +168 -0
- package/public/css/features/home.css +313 -0
- package/public/css/features/retro-terminal.css +88 -0
- package/public/css/features/telegram.css +127 -0
- package/public/css/features/tour.css +148 -0
- package/public/css/features/voice-input.css +60 -0
- package/public/css/features/welcome.css +241 -0
- package/public/css/panels/assistant-bot.css +442 -0
- package/public/css/panels/dev-docs.css +292 -0
- package/public/css/panels/file-explorer.css +322 -0
- package/public/css/panels/git-panel.css +221 -0
- package/public/css/panels/mcp-manager.css +199 -0
- package/public/css/panels/tips-feed.css +353 -0
- package/public/css/ui/commands.css +273 -0
- package/public/css/ui/context-gauge.css +76 -0
- package/public/css/ui/file-picker.css +69 -0
- package/public/css/ui/image-attachments.css +106 -0
- package/public/css/ui/messages.css +884 -0
- package/public/css/ui/modals.css +122 -0
- package/public/css/ui/parallel.css +217 -0
- package/public/css/ui/permissions.css +110 -0
- package/public/css/ui/right-panel.css +481 -0
- package/public/css/ui/sessions.css +689 -0
- package/public/css/ui/status-bar.css +425 -0
- package/public/css/ui/toolbox.css +206 -0
- package/public/data/tips.json +218 -0
- package/public/icons/favicon.png +0 -0
- package/public/icons/icon-192.png +0 -0
- package/public/icons/icon-512.png +0 -0
- package/public/icons/whaly.png +0 -0
- package/public/index.html +1140 -0
- package/public/js/core/api.js +591 -0
- package/public/js/core/constants.js +3 -0
- package/public/js/core/dom.js +270 -0
- package/public/js/core/events.js +10 -0
- package/public/js/core/plugin-loader.js +153 -0
- package/public/js/core/store.js +39 -0
- package/public/js/core/utils.js +25 -0
- package/public/js/core/ws.js +64 -0
- package/public/js/features/agent-monitor.js +222 -0
- package/public/js/features/agents.js +1209 -0
- package/public/js/features/analytics.js +397 -0
- package/public/js/features/attachments.js +251 -0
- package/public/js/features/background-sessions.js +475 -0
- package/public/js/features/chat.js +589 -0
- package/public/js/features/cost-dashboard.js +152 -0
- package/public/js/features/dag-editor.js +399 -0
- package/public/js/features/easter-egg.js +46 -0
- package/public/js/features/home.js +270 -0
- package/public/js/features/projects.js +372 -0
- package/public/js/features/prompts.js +228 -0
- package/public/js/features/sessions.js +332 -0
- package/public/js/features/telegram.js +131 -0
- package/public/js/features/tour.js +210 -0
- package/public/js/features/voice-input.js +185 -0
- package/public/js/features/welcome.js +43 -0
- package/public/js/features/workflows.js +277 -0
- package/public/js/main.js +51 -0
- package/public/js/panels/assistant-bot.js +445 -0
- package/public/js/panels/dev-docs.js +380 -0
- package/public/js/panels/file-explorer.js +486 -0
- package/public/js/panels/git-panel.js +285 -0
- package/public/js/panels/mcp-manager.js +311 -0
- package/public/js/panels/tips-feed.js +303 -0
- package/public/js/ui/commands.js +114 -0
- package/public/js/ui/context-gauge.js +100 -0
- package/public/js/ui/diff.js +124 -0
- package/public/js/ui/disabled-tools.js +36 -0
- package/public/js/ui/export.js +74 -0
- package/public/js/ui/formatting.js +206 -0
- package/public/js/ui/header-dropdowns.js +72 -0
- package/public/js/ui/input-meta.js +71 -0
- package/public/js/ui/max-turns.js +21 -0
- package/public/js/ui/messages.js +387 -0
- package/public/js/ui/model-selector.js +20 -0
- package/public/js/ui/notifications.js +232 -0
- package/public/js/ui/parallel.js +176 -0
- package/public/js/ui/permissions.js +168 -0
- package/public/js/ui/right-panel.js +173 -0
- package/public/js/ui/shortcuts.js +143 -0
- package/public/js/ui/sidebar-toggle.js +29 -0
- package/public/js/ui/status-bar.js +172 -0
- package/public/js/ui/tab-sdk.js +623 -0
- package/public/js/ui/theme.js +38 -0
- package/public/manifest.json +13 -0
- package/public/offline.html +190 -0
- package/public/style.css +42 -0
- package/public/sw.js +91 -0
- package/server/agent-loop.js +385 -0
- package/server/dag-executor.js +265 -0
- package/server/orchestrator.js +514 -0
- package/server/paths.js +61 -0
- package/server/plugin-mount.js +56 -0
- package/server/push-sender.js +31 -0
- package/server/routes/agents.js +294 -0
- package/server/routes/bot.js +45 -0
- package/server/routes/exec.js +35 -0
- package/server/routes/files.js +218 -0
- package/server/routes/mcp.js +82 -0
- package/server/routes/messages.js +36 -0
- package/server/routes/notifications.js +37 -0
- package/server/routes/projects.js +207 -0
- package/server/routes/prompts.js +53 -0
- package/server/routes/sessions.js +103 -0
- package/server/routes/stats.js +143 -0
- package/server/routes/telegram.js +71 -0
- package/server/routes/tips.js +135 -0
- package/server/routes/workflows.js +81 -0
- package/server/summarizer.js +55 -0
- package/server/telegram-poller.js +205 -0
- package/server/telegram-sender.js +304 -0
- package/server/ws-handler.js +926 -0
- package/server.js +179 -0
package/db.js
ADDED
|
@@ -0,0 +1,1198 @@
|
|
|
1
|
+
import Database from "better-sqlite3";
|
|
2
|
+
import { dbPath } from "./server/paths.js";
|
|
3
|
+
|
|
4
|
+
const db = new Database(dbPath);
|
|
5
|
+
|
|
6
|
+
// Enable WAL mode for better concurrent performance
|
|
7
|
+
db.pragma("journal_mode = WAL");
|
|
8
|
+
|
|
9
|
+
// Create tables
|
|
10
|
+
db.exec(`
|
|
11
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
12
|
+
id TEXT PRIMARY KEY,
|
|
13
|
+
claude_session_id TEXT,
|
|
14
|
+
project_name TEXT,
|
|
15
|
+
project_path TEXT,
|
|
16
|
+
created_at INTEGER DEFAULT (unixepoch()),
|
|
17
|
+
last_used_at INTEGER DEFAULT (unixepoch())
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
CREATE TABLE IF NOT EXISTS costs (
|
|
21
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
22
|
+
session_id TEXT REFERENCES sessions(id),
|
|
23
|
+
cost_usd REAL,
|
|
24
|
+
duration_ms INTEGER,
|
|
25
|
+
num_turns INTEGER,
|
|
26
|
+
created_at INTEGER DEFAULT (unixepoch())
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
30
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
31
|
+
session_id TEXT REFERENCES sessions(id),
|
|
32
|
+
role TEXT NOT NULL,
|
|
33
|
+
content TEXT NOT NULL,
|
|
34
|
+
created_at INTEGER DEFAULT (unixepoch())
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
CREATE TABLE IF NOT EXISTS claude_sessions (
|
|
38
|
+
session_id TEXT NOT NULL,
|
|
39
|
+
chat_id TEXT NOT NULL DEFAULT '',
|
|
40
|
+
claude_session_id TEXT NOT NULL,
|
|
41
|
+
PRIMARY KEY (session_id, chat_id)
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
CREATE TABLE IF NOT EXISTS push_subscriptions (
|
|
45
|
+
endpoint TEXT PRIMARY KEY,
|
|
46
|
+
keys_p256dh TEXT NOT NULL,
|
|
47
|
+
keys_auth TEXT NOT NULL,
|
|
48
|
+
created_at INTEGER DEFAULT (unixepoch())
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
CREATE TABLE IF NOT EXISTS todos (
|
|
52
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
53
|
+
text TEXT NOT NULL,
|
|
54
|
+
done INTEGER DEFAULT 0,
|
|
55
|
+
position INTEGER DEFAULT 0,
|
|
56
|
+
created_at INTEGER DEFAULT (unixepoch()),
|
|
57
|
+
updated_at INTEGER DEFAULT (unixepoch())
|
|
58
|
+
);
|
|
59
|
+
`);
|
|
60
|
+
|
|
61
|
+
// Migrations
|
|
62
|
+
try { db.exec(`ALTER TABLE messages ADD COLUMN chat_id TEXT DEFAULT NULL`); } catch { /* exists */ }
|
|
63
|
+
try { db.exec(`ALTER TABLE sessions ADD COLUMN title TEXT DEFAULT NULL`); } catch { /* exists */ }
|
|
64
|
+
try { db.exec(`ALTER TABLE sessions ADD COLUMN pinned INTEGER DEFAULT 0`); } catch { /* exists */ }
|
|
65
|
+
try { db.exec(`ALTER TABLE costs ADD COLUMN input_tokens INTEGER DEFAULT 0`); } catch { /* exists */ }
|
|
66
|
+
try { db.exec(`ALTER TABLE costs ADD COLUMN output_tokens INTEGER DEFAULT 0`); } catch { /* exists */ }
|
|
67
|
+
// New columns for costs table
|
|
68
|
+
try { db.exec(`ALTER TABLE costs ADD COLUMN model TEXT DEFAULT NULL`); } catch { /* exists */ }
|
|
69
|
+
try { db.exec(`ALTER TABLE costs ADD COLUMN stop_reason TEXT DEFAULT NULL`); } catch { /* exists */ }
|
|
70
|
+
try { db.exec(`ALTER TABLE costs ADD COLUMN is_error INTEGER DEFAULT 0`); } catch { /* exists */ }
|
|
71
|
+
try { db.exec(`ALTER TABLE costs ADD COLUMN cache_read_tokens INTEGER DEFAULT 0`); } catch { /* exists */ }
|
|
72
|
+
try { db.exec(`ALTER TABLE costs ADD COLUMN cache_creation_tokens INTEGER DEFAULT 0`); } catch { /* exists */ }
|
|
73
|
+
// New columns for messages table (workflow metadata)
|
|
74
|
+
try { db.exec(`ALTER TABLE messages ADD COLUMN workflow_id TEXT DEFAULT NULL`); } catch { /* exists */ }
|
|
75
|
+
try { db.exec(`ALTER TABLE messages ADD COLUMN workflow_step_index INTEGER DEFAULT NULL`); } catch { /* exists */ }
|
|
76
|
+
try { db.exec(`ALTER TABLE messages ADD COLUMN workflow_step_label TEXT DEFAULT NULL`); } catch { /* exists */ }
|
|
77
|
+
// AI-generated session summary
|
|
78
|
+
try { db.exec(`ALTER TABLE sessions ADD COLUMN summary TEXT DEFAULT NULL`); } catch { /* exists */ }
|
|
79
|
+
// Todo archive
|
|
80
|
+
try { db.exec(`ALTER TABLE todos ADD COLUMN archived INTEGER DEFAULT 0`); } catch { /* exists */ }
|
|
81
|
+
// Todo priority (0=none, 1=low, 2=medium, 3=high)
|
|
82
|
+
try { db.exec(`ALTER TABLE todos ADD COLUMN priority INTEGER DEFAULT 0`); } catch { /* exists */ }
|
|
83
|
+
|
|
84
|
+
// Agent context (shared memory between agents in a chain/orchestration run)
|
|
85
|
+
db.exec(`
|
|
86
|
+
CREATE TABLE IF NOT EXISTS agent_context (
|
|
87
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
88
|
+
run_id TEXT NOT NULL,
|
|
89
|
+
agent_id TEXT NOT NULL,
|
|
90
|
+
key TEXT NOT NULL,
|
|
91
|
+
value TEXT NOT NULL,
|
|
92
|
+
created_at INTEGER DEFAULT (unixepoch()),
|
|
93
|
+
UNIQUE(run_id, agent_id, key)
|
|
94
|
+
);
|
|
95
|
+
CREATE INDEX IF NOT EXISTS idx_agent_context_run ON agent_context(run_id);
|
|
96
|
+
`);
|
|
97
|
+
|
|
98
|
+
// Agent runs table (monitoring dashboard)
|
|
99
|
+
db.exec(`
|
|
100
|
+
CREATE TABLE IF NOT EXISTS agent_runs (
|
|
101
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
102
|
+
run_id TEXT NOT NULL,
|
|
103
|
+
agent_id TEXT NOT NULL,
|
|
104
|
+
agent_title TEXT NOT NULL,
|
|
105
|
+
run_type TEXT NOT NULL DEFAULT 'single',
|
|
106
|
+
parent_id TEXT,
|
|
107
|
+
status TEXT NOT NULL DEFAULT 'running',
|
|
108
|
+
turns INTEGER DEFAULT 0,
|
|
109
|
+
cost_usd REAL DEFAULT 0,
|
|
110
|
+
duration_ms INTEGER DEFAULT 0,
|
|
111
|
+
input_tokens INTEGER DEFAULT 0,
|
|
112
|
+
output_tokens INTEGER DEFAULT 0,
|
|
113
|
+
error TEXT,
|
|
114
|
+
started_at INTEGER DEFAULT (unixepoch()),
|
|
115
|
+
completed_at INTEGER
|
|
116
|
+
);
|
|
117
|
+
CREATE INDEX IF NOT EXISTS idx_agent_runs_agent ON agent_runs(agent_id);
|
|
118
|
+
CREATE INDEX IF NOT EXISTS idx_agent_runs_started ON agent_runs(started_at);
|
|
119
|
+
CREATE INDEX IF NOT EXISTS idx_agent_runs_run_id ON agent_runs(run_id);
|
|
120
|
+
`);
|
|
121
|
+
|
|
122
|
+
// Brags table
|
|
123
|
+
db.exec(`
|
|
124
|
+
CREATE TABLE IF NOT EXISTS brags (
|
|
125
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
126
|
+
todo_id INTEGER REFERENCES todos(id),
|
|
127
|
+
text TEXT NOT NULL,
|
|
128
|
+
summary TEXT NOT NULL,
|
|
129
|
+
created_at INTEGER DEFAULT (unixepoch())
|
|
130
|
+
);
|
|
131
|
+
`);
|
|
132
|
+
|
|
133
|
+
// Indexes for query performance
|
|
134
|
+
db.exec(`
|
|
135
|
+
CREATE INDEX IF NOT EXISTS idx_messages_session_id ON messages(session_id);
|
|
136
|
+
CREATE INDEX IF NOT EXISTS idx_messages_session_chat ON messages(session_id, chat_id);
|
|
137
|
+
CREATE INDEX IF NOT EXISTS idx_costs_session_id ON costs(session_id);
|
|
138
|
+
CREATE INDEX IF NOT EXISTS idx_costs_created_at ON costs(created_at);
|
|
139
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_project_path ON sessions(project_path);
|
|
140
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_pinned_last_used ON sessions(pinned DESC, last_used_at DESC);
|
|
141
|
+
`);
|
|
142
|
+
|
|
143
|
+
// Deduplicated mode CASE subquery — used in 4 session listing queries
|
|
144
|
+
const MODE_CASE = `
|
|
145
|
+
CASE
|
|
146
|
+
WHEN EXISTS (SELECT 1 FROM messages m WHERE m.session_id = s.id AND m.chat_id IS NOT NULL)
|
|
147
|
+
AND EXISTS (SELECT 1 FROM messages m WHERE m.session_id = s.id AND m.chat_id IS NULL)
|
|
148
|
+
THEN 'both'
|
|
149
|
+
WHEN EXISTS (SELECT 1 FROM messages m WHERE m.session_id = s.id AND m.chat_id IS NOT NULL)
|
|
150
|
+
THEN 'parallel'
|
|
151
|
+
ELSE 'single'
|
|
152
|
+
END AS mode`;
|
|
153
|
+
|
|
154
|
+
// Prepared statements
|
|
155
|
+
const stmts = {
|
|
156
|
+
createSession: db.prepare(
|
|
157
|
+
`INSERT OR IGNORE INTO sessions (id, claude_session_id, project_name, project_path)
|
|
158
|
+
VALUES (?, ?, ?, ?)`
|
|
159
|
+
),
|
|
160
|
+
updateClaudeSessionId: db.prepare(
|
|
161
|
+
`UPDATE sessions SET claude_session_id = ? WHERE id = ?`
|
|
162
|
+
),
|
|
163
|
+
getSession: db.prepare(`SELECT * FROM sessions WHERE id = ?`),
|
|
164
|
+
listSessions: db.prepare(
|
|
165
|
+
`SELECT s.*, ${MODE_CASE}
|
|
166
|
+
FROM sessions s ORDER BY s.pinned DESC, s.last_used_at DESC LIMIT ?`
|
|
167
|
+
),
|
|
168
|
+
listSessionsByProject: db.prepare(
|
|
169
|
+
`SELECT s.*, ${MODE_CASE}
|
|
170
|
+
FROM sessions s WHERE s.project_path = ? ORDER BY s.pinned DESC, s.last_used_at DESC LIMIT ?`
|
|
171
|
+
),
|
|
172
|
+
touchSession: db.prepare(
|
|
173
|
+
`UPDATE sessions SET last_used_at = unixepoch() WHERE id = ?`
|
|
174
|
+
),
|
|
175
|
+
addCost: db.prepare(
|
|
176
|
+
`INSERT INTO costs (session_id, cost_usd, duration_ms, num_turns, input_tokens, output_tokens, model, stop_reason, is_error, cache_read_tokens, cache_creation_tokens) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
177
|
+
),
|
|
178
|
+
addMessage: db.prepare(
|
|
179
|
+
`INSERT INTO messages (session_id, role, content, chat_id, workflow_id, workflow_step_index, workflow_step_label) VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
180
|
+
),
|
|
181
|
+
getMessages: db.prepare(
|
|
182
|
+
`SELECT * FROM messages WHERE session_id = ? ORDER BY id ASC`
|
|
183
|
+
),
|
|
184
|
+
getMessagesByChatId: db.prepare(
|
|
185
|
+
`SELECT * FROM messages WHERE session_id = ? AND chat_id = ? ORDER BY id ASC`
|
|
186
|
+
),
|
|
187
|
+
getMessagesNoChatId: db.prepare(
|
|
188
|
+
`SELECT * FROM messages WHERE session_id = ? AND chat_id IS NULL ORDER BY id ASC`
|
|
189
|
+
),
|
|
190
|
+
getTotalCost: db.prepare(`SELECT COALESCE(SUM(cost_usd), 0) AS total FROM costs`),
|
|
191
|
+
getProjectCost: db.prepare(
|
|
192
|
+
`SELECT COALESCE(SUM(c.cost_usd), 0) AS total
|
|
193
|
+
FROM costs c JOIN sessions s ON c.session_id = s.id
|
|
194
|
+
WHERE s.project_path = ?`
|
|
195
|
+
),
|
|
196
|
+
setClaudeSession: db.prepare(
|
|
197
|
+
`INSERT OR REPLACE INTO claude_sessions (session_id, chat_id, claude_session_id) VALUES (?, ?, ?)`
|
|
198
|
+
),
|
|
199
|
+
getClaudeSessionId: db.prepare(
|
|
200
|
+
`SELECT claude_session_id FROM claude_sessions WHERE session_id = ? AND chat_id = ?`
|
|
201
|
+
),
|
|
202
|
+
allClaudeSessions: db.prepare(
|
|
203
|
+
`SELECT * FROM claude_sessions`
|
|
204
|
+
),
|
|
205
|
+
updateSessionTitle: db.prepare(
|
|
206
|
+
`UPDATE sessions SET title = ? WHERE id = ?`
|
|
207
|
+
),
|
|
208
|
+
toggleSessionPin: db.prepare(
|
|
209
|
+
`UPDATE sessions SET pinned = CASE WHEN pinned = 1 THEN 0 ELSE 1 END WHERE id = ?`
|
|
210
|
+
),
|
|
211
|
+
updateSessionSummary: db.prepare(
|
|
212
|
+
`UPDATE sessions SET summary = ? WHERE id = ?`
|
|
213
|
+
),
|
|
214
|
+
searchSessions: db.prepare(
|
|
215
|
+
`SELECT s.*, ${MODE_CASE}
|
|
216
|
+
FROM sessions s WHERE s.project_path = ? AND (s.title LIKE ? OR s.project_name LIKE ?) ORDER BY s.pinned DESC, s.last_used_at DESC LIMIT ?`
|
|
217
|
+
),
|
|
218
|
+
searchSessionsAll: db.prepare(
|
|
219
|
+
`SELECT s.*, ${MODE_CASE}
|
|
220
|
+
FROM sessions s WHERE (s.title LIKE ? OR s.project_name LIKE ?) ORDER BY s.pinned DESC, s.last_used_at DESC LIMIT ?`
|
|
221
|
+
),
|
|
222
|
+
getSessionCosts: db.prepare(
|
|
223
|
+
`SELECT s.id, s.title, s.project_name, s.last_used_at,
|
|
224
|
+
COALESCE(SUM(c.cost_usd), 0) AS total_cost,
|
|
225
|
+
COALESCE(SUM(c.num_turns), 0) AS turns,
|
|
226
|
+
COALESCE(SUM(c.input_tokens), 0) AS input_tokens,
|
|
227
|
+
COALESCE(SUM(c.output_tokens), 0) AS output_tokens
|
|
228
|
+
FROM sessions s
|
|
229
|
+
LEFT JOIN costs c ON c.session_id = s.id
|
|
230
|
+
WHERE s.project_path = ?
|
|
231
|
+
GROUP BY s.id
|
|
232
|
+
ORDER BY total_cost DESC`
|
|
233
|
+
),
|
|
234
|
+
getSessionCostsAll: db.prepare(
|
|
235
|
+
`SELECT s.id, s.title, s.project_name, s.last_used_at,
|
|
236
|
+
COALESCE(SUM(c.cost_usd), 0) AS total_cost,
|
|
237
|
+
COALESCE(SUM(c.num_turns), 0) AS turns,
|
|
238
|
+
COALESCE(SUM(c.input_tokens), 0) AS input_tokens,
|
|
239
|
+
COALESCE(SUM(c.output_tokens), 0) AS output_tokens
|
|
240
|
+
FROM sessions s
|
|
241
|
+
LEFT JOIN costs c ON c.session_id = s.id
|
|
242
|
+
GROUP BY s.id
|
|
243
|
+
ORDER BY total_cost DESC`
|
|
244
|
+
),
|
|
245
|
+
getCostTimeline: db.prepare(
|
|
246
|
+
`SELECT date(c.created_at, 'unixepoch') AS date,
|
|
247
|
+
SUM(c.cost_usd) AS cost
|
|
248
|
+
FROM costs c
|
|
249
|
+
WHERE c.created_at >= unixepoch() - 30 * 86400
|
|
250
|
+
GROUP BY date(c.created_at, 'unixepoch')
|
|
251
|
+
ORDER BY date ASC`
|
|
252
|
+
),
|
|
253
|
+
// Todo CRUD
|
|
254
|
+
listTodos: db.prepare(`SELECT * FROM todos WHERE archived = 0 ORDER BY position ASC, id ASC`),
|
|
255
|
+
listArchivedTodos: db.prepare(`SELECT * FROM todos WHERE archived = 1 ORDER BY updated_at DESC`),
|
|
256
|
+
createTodo: db.prepare(`INSERT INTO todos (text, position) VALUES (?, (SELECT COALESCE(MAX(position),0)+1 FROM todos))`),
|
|
257
|
+
updateTodo: db.prepare(`UPDATE todos SET text = COALESCE(?, text), done = COALESCE(?, done), priority = COALESCE(?, priority), updated_at = unixepoch() WHERE id = ?`),
|
|
258
|
+
archiveTodo: db.prepare(`UPDATE todos SET archived = ?, updated_at = unixepoch() WHERE id = ?`),
|
|
259
|
+
deleteTodo: db.prepare(`DELETE FROM todos WHERE id = ?`),
|
|
260
|
+
todoCounts: db.prepare(`
|
|
261
|
+
SELECT
|
|
262
|
+
(SELECT COUNT(*) FROM todos WHERE archived = 0) AS active,
|
|
263
|
+
(SELECT COUNT(*) FROM todos WHERE archived = 1) AS archived,
|
|
264
|
+
(SELECT COUNT(*) FROM brags) AS brags
|
|
265
|
+
`),
|
|
266
|
+
|
|
267
|
+
// Brag CRUD
|
|
268
|
+
createBrag: db.prepare(`INSERT INTO brags (todo_id, text, summary) VALUES (?, ?, ?)`),
|
|
269
|
+
listBrags: db.prepare(`SELECT * FROM brags ORDER BY created_at DESC`),
|
|
270
|
+
deleteBrag: db.prepare(`DELETE FROM brags WHERE id = ?`),
|
|
271
|
+
|
|
272
|
+
yearlyActivity: db.prepare(
|
|
273
|
+
`SELECT
|
|
274
|
+
date(c.created_at, 'unixepoch') AS date,
|
|
275
|
+
COUNT(DISTINCT c.session_id) AS sessions,
|
|
276
|
+
COUNT(*) AS queries,
|
|
277
|
+
COALESCE(SUM(c.cost_usd), 0) AS cost,
|
|
278
|
+
COALESCE(SUM(c.input_tokens), 0) AS input_tokens,
|
|
279
|
+
COALESCE(SUM(c.output_tokens), 0) AS output_tokens,
|
|
280
|
+
COALESCE(SUM(c.num_turns), 0) AS turns
|
|
281
|
+
FROM costs c
|
|
282
|
+
WHERE c.created_at >= unixepoch() - 365 * 86400
|
|
283
|
+
GROUP BY date(c.created_at, 'unixepoch')
|
|
284
|
+
ORDER BY date ASC`
|
|
285
|
+
),
|
|
286
|
+
getCostTimelineByProject: db.prepare(
|
|
287
|
+
`SELECT date(c.created_at, 'unixepoch') AS date,
|
|
288
|
+
SUM(c.cost_usd) AS cost
|
|
289
|
+
FROM costs c
|
|
290
|
+
JOIN sessions s ON c.session_id = s.id
|
|
291
|
+
WHERE s.project_path = ? AND c.created_at >= unixepoch() - 30 * 86400
|
|
292
|
+
GROUP BY date(c.created_at, 'unixepoch')
|
|
293
|
+
ORDER BY date ASC`
|
|
294
|
+
),
|
|
295
|
+
getTotalTokens: db.prepare(
|
|
296
|
+
`SELECT COALESCE(SUM(input_tokens), 0) AS input_tokens,
|
|
297
|
+
COALESCE(SUM(output_tokens), 0) AS output_tokens
|
|
298
|
+
FROM costs`
|
|
299
|
+
),
|
|
300
|
+
getProjectTokens: db.prepare(
|
|
301
|
+
`SELECT COALESCE(SUM(c.input_tokens), 0) AS input_tokens,
|
|
302
|
+
COALESCE(SUM(c.output_tokens), 0) AS output_tokens
|
|
303
|
+
FROM costs c JOIN sessions s ON c.session_id = s.id
|
|
304
|
+
WHERE s.project_path = ?`
|
|
305
|
+
),
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
export function createSession(id, claudeSessionId, projectName, projectPath) {
|
|
309
|
+
stmts.createSession.run(id, claudeSessionId, projectName, projectPath);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export function updateClaudeSessionId(id, claudeSessionId) {
|
|
313
|
+
stmts.updateClaudeSessionId.run(claudeSessionId, id);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export function getSession(id) {
|
|
317
|
+
return stmts.getSession.get(id);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export function listSessions(limit = 20, projectPath) {
|
|
321
|
+
if (projectPath) {
|
|
322
|
+
return stmts.listSessionsByProject.all(projectPath, limit);
|
|
323
|
+
}
|
|
324
|
+
return stmts.listSessions.all(limit);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export function touchSession(id) {
|
|
328
|
+
stmts.touchSession.run(id);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export function addCost(sessionId, costUsd, durationMs, numTurns, inputTokens = 0, outputTokens = 0, { model = null, stopReason = null, isError = 0, cacheReadTokens = 0, cacheCreationTokens = 0 } = {}) {
|
|
332
|
+
stmts.addCost.run(sessionId, costUsd, durationMs, numTurns, inputTokens, outputTokens, model, stopReason, isError, cacheReadTokens, cacheCreationTokens);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export function getTotalCost() {
|
|
336
|
+
return stmts.getTotalCost.get().total;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export function getProjectCost(projectPath) {
|
|
340
|
+
return stmts.getProjectCost.get(projectPath).total;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
export function addMessage(sessionId, role, content, chatId = null, workflowMeta = null) {
|
|
344
|
+
stmts.addMessage.run(sessionId, role, content, chatId, workflowMeta?.workflowId ?? null, workflowMeta?.stepIndex ?? null, workflowMeta?.stepLabel ?? null);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export function getMessages(sessionId) {
|
|
348
|
+
return stmts.getMessages.all(sessionId);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export function getMessagesByChatId(sessionId, chatId) {
|
|
352
|
+
return stmts.getMessagesByChatId.all(sessionId, chatId);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export function getMessagesNoChatId(sessionId) {
|
|
356
|
+
return stmts.getMessagesNoChatId.all(sessionId);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export function setClaudeSession(sessionId, chatId, claudeSessionId) {
|
|
360
|
+
stmts.setClaudeSession.run(sessionId, chatId, claudeSessionId);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export function getClaudeSessionId(sessionId, chatId) {
|
|
364
|
+
const row = stmts.getClaudeSessionId.get(sessionId, chatId);
|
|
365
|
+
return row ? row.claude_session_id : null;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
export function allClaudeSessions() {
|
|
369
|
+
return stmts.allClaudeSessions.all();
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
export function updateSessionTitle(id, title) {
|
|
373
|
+
stmts.updateSessionTitle.run(title, id);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export function toggleSessionPin(id) {
|
|
377
|
+
stmts.toggleSessionPin.run(id);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export function updateSessionSummary(id, summary) {
|
|
381
|
+
stmts.updateSessionSummary.run(summary, id);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
export function searchSessions(query, limit = 20, projectPath) {
|
|
385
|
+
const pattern = `%${query}%`;
|
|
386
|
+
if (projectPath) {
|
|
387
|
+
return stmts.searchSessions.all(projectPath, pattern, pattern, limit);
|
|
388
|
+
}
|
|
389
|
+
return stmts.searchSessionsAll.all(pattern, pattern, limit);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
export const deleteSession = db.transaction((id) => {
|
|
393
|
+
db.prepare("DELETE FROM claude_sessions WHERE session_id = ?").run(id);
|
|
394
|
+
db.prepare("DELETE FROM costs WHERE session_id = ?").run(id);
|
|
395
|
+
db.prepare("DELETE FROM messages WHERE session_id = ?").run(id);
|
|
396
|
+
db.prepare("DELETE FROM sessions WHERE id = ?").run(id);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
export function getSessionCosts(projectPath) {
|
|
400
|
+
if (projectPath) {
|
|
401
|
+
return stmts.getSessionCosts.all(projectPath);
|
|
402
|
+
}
|
|
403
|
+
return stmts.getSessionCostsAll.all();
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
export function getCostTimeline(projectPath) {
|
|
407
|
+
if (projectPath) {
|
|
408
|
+
return stmts.getCostTimelineByProject.all(projectPath);
|
|
409
|
+
}
|
|
410
|
+
return stmts.getCostTimeline.all();
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
export function getTotalTokens() {
|
|
414
|
+
return stmts.getTotalTokens.get();
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
export function getProjectTokens(projectPath) {
|
|
418
|
+
return stmts.getProjectTokens.get(projectPath);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// ── Error categorization CASE (reused in multiple queries) ────
|
|
422
|
+
const ERROR_CATEGORY_CASE = `
|
|
423
|
+
CASE
|
|
424
|
+
WHEN json_extract(tr.content, '$.content') LIKE '%ENOENT%'
|
|
425
|
+
OR json_extract(tr.content, '$.content') LIKE '%does not exist%'
|
|
426
|
+
OR json_extract(tr.content, '$.content') LIKE '%No such file%'
|
|
427
|
+
THEN 'File Not Found'
|
|
428
|
+
WHEN json_extract(tr.content, '$.content') LIKE '%Denied by user%'
|
|
429
|
+
OR json_extract(tr.content, '$.content') LIKE '%Aborted by user%'
|
|
430
|
+
THEN 'User Denied'
|
|
431
|
+
WHEN json_extract(tr.content, '$.content') LIKE '%timed out%'
|
|
432
|
+
THEN 'Timeout'
|
|
433
|
+
WHEN json_extract(tr.content, '$.content') LIKE '%File has not been read%'
|
|
434
|
+
OR json_extract(tr.content, '$.content') LIKE '%File has been modified%'
|
|
435
|
+
THEN 'File State Error'
|
|
436
|
+
WHEN json_extract(tr.content, '$.content') LIKE '%EISDIR%'
|
|
437
|
+
OR json_extract(tr.content, '$.content') LIKE '%illegal operation on a directory%'
|
|
438
|
+
THEN 'Directory Error'
|
|
439
|
+
WHEN json_extract(tr.content, '$.content') LIKE '%Found % matches%'
|
|
440
|
+
THEN 'Multiple Matches'
|
|
441
|
+
WHEN json_extract(tr.content, '$.content') LIKE '%command not found%'
|
|
442
|
+
THEN 'Command Not Found'
|
|
443
|
+
WHEN json_extract(tr.content, '$.content') LIKE '%npm error%'
|
|
444
|
+
OR json_extract(tr.content, '$.content') LIKE '%SyntaxError%'
|
|
445
|
+
OR json_extract(tr.content, '$.content') LIKE '%error TS%'
|
|
446
|
+
THEN 'Build/Runtime Error'
|
|
447
|
+
ELSE 'Other'
|
|
448
|
+
END`;
|
|
449
|
+
|
|
450
|
+
// ── Analytics queries ──────────────────────────────────────────
|
|
451
|
+
|
|
452
|
+
const analyticsStmts = {
|
|
453
|
+
overviewAll: db.prepare(`
|
|
454
|
+
SELECT
|
|
455
|
+
(SELECT COUNT(*) FROM sessions) AS sessions,
|
|
456
|
+
COUNT(*) AS queries,
|
|
457
|
+
COALESCE(SUM(cost_usd), 0) AS totalCost,
|
|
458
|
+
COALESCE(SUM(num_turns), 0) AS totalTurns,
|
|
459
|
+
COALESCE(SUM(output_tokens), 0) AS totalOutputTokens
|
|
460
|
+
FROM costs
|
|
461
|
+
`),
|
|
462
|
+
overviewByProject: db.prepare(`
|
|
463
|
+
SELECT
|
|
464
|
+
COUNT(DISTINCT s.id) AS sessions,
|
|
465
|
+
COUNT(c.id) AS queries,
|
|
466
|
+
COALESCE(SUM(c.cost_usd), 0) AS totalCost,
|
|
467
|
+
COALESCE(SUM(c.num_turns), 0) AS totalTurns,
|
|
468
|
+
COALESCE(SUM(c.output_tokens), 0) AS totalOutputTokens
|
|
469
|
+
FROM sessions s
|
|
470
|
+
LEFT JOIN costs c ON c.session_id = s.id
|
|
471
|
+
WHERE s.project_path = ?
|
|
472
|
+
`),
|
|
473
|
+
errorRateAll: db.prepare(`
|
|
474
|
+
SELECT
|
|
475
|
+
COUNT(CASE WHEN json_extract(content, '$.isError') = 1 THEN 1 END) AS errors,
|
|
476
|
+
COUNT(*) AS total
|
|
477
|
+
FROM messages WHERE role = 'tool_result'
|
|
478
|
+
`),
|
|
479
|
+
errorRateByProject: db.prepare(`
|
|
480
|
+
SELECT
|
|
481
|
+
COUNT(CASE WHEN json_extract(m.content, '$.isError') = 1 THEN 1 END) AS errors,
|
|
482
|
+
COUNT(*) AS total
|
|
483
|
+
FROM messages m
|
|
484
|
+
JOIN sessions s ON m.session_id = s.id
|
|
485
|
+
WHERE m.role = 'tool_result' AND s.project_path = ?
|
|
486
|
+
`),
|
|
487
|
+
dailyBreakdownAll: db.prepare(`
|
|
488
|
+
SELECT
|
|
489
|
+
date(c.created_at, 'unixepoch') AS date,
|
|
490
|
+
COUNT(*) AS queries,
|
|
491
|
+
SUM(c.cost_usd) AS cost,
|
|
492
|
+
SUM(c.num_turns) AS turns,
|
|
493
|
+
SUM(c.output_tokens) AS output_tok
|
|
494
|
+
FROM costs c
|
|
495
|
+
WHERE c.created_at >= unixepoch() - 30 * 86400
|
|
496
|
+
GROUP BY date(c.created_at, 'unixepoch')
|
|
497
|
+
ORDER BY date ASC
|
|
498
|
+
`),
|
|
499
|
+
dailyBreakdownByProject: db.prepare(`
|
|
500
|
+
SELECT
|
|
501
|
+
date(c.created_at, 'unixepoch') AS date,
|
|
502
|
+
COUNT(*) AS queries,
|
|
503
|
+
SUM(c.cost_usd) AS cost,
|
|
504
|
+
SUM(c.num_turns) AS turns,
|
|
505
|
+
SUM(c.output_tokens) AS output_tok
|
|
506
|
+
FROM costs c
|
|
507
|
+
JOIN sessions s ON c.session_id = s.id
|
|
508
|
+
WHERE s.project_path = ? AND c.created_at >= unixepoch() - 30 * 86400
|
|
509
|
+
GROUP BY date(c.created_at, 'unixepoch')
|
|
510
|
+
ORDER BY date ASC
|
|
511
|
+
`),
|
|
512
|
+
hourlyActivityAll: db.prepare(`
|
|
513
|
+
SELECT
|
|
514
|
+
CAST(strftime('%H', c.created_at, 'unixepoch', 'localtime') AS INTEGER) AS hour,
|
|
515
|
+
COUNT(*) AS queries,
|
|
516
|
+
SUM(c.cost_usd) AS cost
|
|
517
|
+
FROM costs c
|
|
518
|
+
GROUP BY strftime('%H', c.created_at, 'unixepoch', 'localtime')
|
|
519
|
+
ORDER BY hour ASC
|
|
520
|
+
`),
|
|
521
|
+
hourlyActivityByProject: db.prepare(`
|
|
522
|
+
SELECT
|
|
523
|
+
CAST(strftime('%H', c.created_at, 'unixepoch', 'localtime') AS INTEGER) AS hour,
|
|
524
|
+
COUNT(*) AS queries,
|
|
525
|
+
SUM(c.cost_usd) AS cost
|
|
526
|
+
FROM costs c
|
|
527
|
+
JOIN sessions s ON c.session_id = s.id
|
|
528
|
+
WHERE s.project_path = ?
|
|
529
|
+
GROUP BY strftime('%H', c.created_at, 'unixepoch', 'localtime')
|
|
530
|
+
ORDER BY hour ASC
|
|
531
|
+
`),
|
|
532
|
+
projectBreakdown: db.prepare(`
|
|
533
|
+
SELECT
|
|
534
|
+
s.project_name AS name,
|
|
535
|
+
s.project_path AS path,
|
|
536
|
+
COUNT(DISTINCT s.id) AS sessions,
|
|
537
|
+
COUNT(c.id) AS queries,
|
|
538
|
+
COALESCE(SUM(c.cost_usd), 0) AS totalCost,
|
|
539
|
+
CASE WHEN COUNT(DISTINCT s.id) > 0
|
|
540
|
+
THEN COALESCE(SUM(c.cost_usd), 0) / COUNT(DISTINCT s.id)
|
|
541
|
+
ELSE 0 END AS avgCost,
|
|
542
|
+
CASE WHEN COUNT(DISTINCT s.id) > 0
|
|
543
|
+
THEN COALESCE(SUM(c.num_turns), 0) / COUNT(DISTINCT s.id)
|
|
544
|
+
ELSE 0 END AS avgTurns
|
|
545
|
+
FROM sessions s
|
|
546
|
+
LEFT JOIN costs c ON c.session_id = s.id
|
|
547
|
+
GROUP BY s.project_path
|
|
548
|
+
ORDER BY totalCost DESC
|
|
549
|
+
`),
|
|
550
|
+
topSessionsAll: db.prepare(`
|
|
551
|
+
SELECT
|
|
552
|
+
s.title,
|
|
553
|
+
s.project_name AS project,
|
|
554
|
+
COALESCE(SUM(c.cost_usd), 0) AS cost,
|
|
555
|
+
COALESCE(SUM(c.num_turns), 0) AS turns,
|
|
556
|
+
COUNT(c.id) AS queries,
|
|
557
|
+
COALESCE(SUM(c.duration_ms), 0) / 60000.0 AS duration_min
|
|
558
|
+
FROM sessions s
|
|
559
|
+
LEFT JOIN costs c ON c.session_id = s.id
|
|
560
|
+
GROUP BY s.id
|
|
561
|
+
HAVING cost > 0
|
|
562
|
+
ORDER BY cost DESC
|
|
563
|
+
LIMIT 10
|
|
564
|
+
`),
|
|
565
|
+
topSessionsByProject: db.prepare(`
|
|
566
|
+
SELECT
|
|
567
|
+
s.title,
|
|
568
|
+
s.project_name AS project,
|
|
569
|
+
COALESCE(SUM(c.cost_usd), 0) AS cost,
|
|
570
|
+
COALESCE(SUM(c.num_turns), 0) AS turns,
|
|
571
|
+
COUNT(c.id) AS queries,
|
|
572
|
+
COALESCE(SUM(c.duration_ms), 0) / 60000.0 AS duration_min
|
|
573
|
+
FROM sessions s
|
|
574
|
+
LEFT JOIN costs c ON c.session_id = s.id
|
|
575
|
+
WHERE s.project_path = ?
|
|
576
|
+
GROUP BY s.id
|
|
577
|
+
HAVING cost > 0
|
|
578
|
+
ORDER BY cost DESC
|
|
579
|
+
LIMIT 10
|
|
580
|
+
`),
|
|
581
|
+
toolUsageAll: db.prepare(`
|
|
582
|
+
SELECT
|
|
583
|
+
json_extract(content, '$.name') AS name,
|
|
584
|
+
COUNT(*) AS count
|
|
585
|
+
FROM messages
|
|
586
|
+
WHERE role = 'tool' AND json_extract(content, '$.name') IS NOT NULL
|
|
587
|
+
GROUP BY json_extract(content, '$.name')
|
|
588
|
+
ORDER BY count DESC
|
|
589
|
+
`),
|
|
590
|
+
toolUsageByProject: db.prepare(`
|
|
591
|
+
SELECT
|
|
592
|
+
json_extract(m.content, '$.name') AS name,
|
|
593
|
+
COUNT(*) AS count
|
|
594
|
+
FROM messages m
|
|
595
|
+
JOIN sessions s ON m.session_id = s.id
|
|
596
|
+
WHERE m.role = 'tool' AND s.project_path = ? AND json_extract(m.content, '$.name') IS NOT NULL
|
|
597
|
+
GROUP BY json_extract(m.content, '$.name')
|
|
598
|
+
ORDER BY count DESC
|
|
599
|
+
`),
|
|
600
|
+
toolErrorsAll: db.prepare(`
|
|
601
|
+
SELECT
|
|
602
|
+
json_extract(t.content, '$.name') AS name,
|
|
603
|
+
COUNT(CASE WHEN json_extract(tr.content, '$.isError') = 1 THEN 1 END) AS errors,
|
|
604
|
+
COUNT(*) AS total,
|
|
605
|
+
CAST(COUNT(CASE WHEN json_extract(tr.content, '$.isError') = 1 THEN 1 END) AS REAL) / NULLIF(COUNT(*), 0) * 100 AS errorRate
|
|
606
|
+
FROM messages t
|
|
607
|
+
JOIN messages tr ON tr.session_id = t.session_id
|
|
608
|
+
AND tr.role = 'tool_result'
|
|
609
|
+
AND json_extract(tr.content, '$.toolUseId') = json_extract(t.content, '$.id')
|
|
610
|
+
WHERE t.role = 'tool'
|
|
611
|
+
GROUP BY json_extract(t.content, '$.name')
|
|
612
|
+
HAVING errors > 0
|
|
613
|
+
ORDER BY errors DESC
|
|
614
|
+
`),
|
|
615
|
+
toolErrorsByProject: db.prepare(`
|
|
616
|
+
SELECT
|
|
617
|
+
json_extract(t.content, '$.name') AS name,
|
|
618
|
+
COUNT(CASE WHEN json_extract(tr.content, '$.isError') = 1 THEN 1 END) AS errors,
|
|
619
|
+
COUNT(*) AS total,
|
|
620
|
+
CAST(COUNT(CASE WHEN json_extract(tr.content, '$.isError') = 1 THEN 1 END) AS REAL) / NULLIF(COUNT(*), 0) * 100 AS errorRate
|
|
621
|
+
FROM messages t
|
|
622
|
+
JOIN messages tr ON tr.session_id = t.session_id
|
|
623
|
+
AND tr.role = 'tool_result'
|
|
624
|
+
AND json_extract(tr.content, '$.toolUseId') = json_extract(t.content, '$.id')
|
|
625
|
+
JOIN sessions s ON t.session_id = s.id
|
|
626
|
+
WHERE t.role = 'tool' AND s.project_path = ?
|
|
627
|
+
GROUP BY json_extract(t.content, '$.name')
|
|
628
|
+
HAVING errors > 0
|
|
629
|
+
ORDER BY errors DESC
|
|
630
|
+
`),
|
|
631
|
+
sessionDepthAll: db.prepare(`
|
|
632
|
+
SELECT
|
|
633
|
+
CASE
|
|
634
|
+
WHEN cnt = 1 THEN '1 query'
|
|
635
|
+
WHEN cnt BETWEEN 2 AND 3 THEN '2-3'
|
|
636
|
+
WHEN cnt BETWEEN 4 AND 6 THEN '4-6'
|
|
637
|
+
WHEN cnt BETWEEN 7 AND 10 THEN '7-10'
|
|
638
|
+
ELSE '10+'
|
|
639
|
+
END AS bucket,
|
|
640
|
+
COUNT(*) AS count,
|
|
641
|
+
AVG(total_cost) AS avgCost
|
|
642
|
+
FROM (
|
|
643
|
+
SELECT s.id, COUNT(c.id) AS cnt, COALESCE(SUM(c.cost_usd), 0) AS total_cost
|
|
644
|
+
FROM sessions s
|
|
645
|
+
LEFT JOIN costs c ON c.session_id = s.id
|
|
646
|
+
GROUP BY s.id
|
|
647
|
+
HAVING cnt > 0
|
|
648
|
+
)
|
|
649
|
+
GROUP BY bucket
|
|
650
|
+
ORDER BY MIN(cnt)
|
|
651
|
+
`),
|
|
652
|
+
sessionDepthByProject: db.prepare(`
|
|
653
|
+
SELECT
|
|
654
|
+
CASE
|
|
655
|
+
WHEN cnt = 1 THEN '1 query'
|
|
656
|
+
WHEN cnt BETWEEN 2 AND 3 THEN '2-3'
|
|
657
|
+
WHEN cnt BETWEEN 4 AND 6 THEN '4-6'
|
|
658
|
+
WHEN cnt BETWEEN 7 AND 10 THEN '7-10'
|
|
659
|
+
ELSE '10+'
|
|
660
|
+
END AS bucket,
|
|
661
|
+
COUNT(*) AS count,
|
|
662
|
+
AVG(total_cost) AS avgCost
|
|
663
|
+
FROM (
|
|
664
|
+
SELECT s.id, COUNT(c.id) AS cnt, COALESCE(SUM(c.cost_usd), 0) AS total_cost
|
|
665
|
+
FROM sessions s
|
|
666
|
+
LEFT JOIN costs c ON c.session_id = s.id
|
|
667
|
+
WHERE s.project_path = ?
|
|
668
|
+
GROUP BY s.id
|
|
669
|
+
HAVING cnt > 0
|
|
670
|
+
)
|
|
671
|
+
GROUP BY bucket
|
|
672
|
+
ORDER BY MIN(cnt)
|
|
673
|
+
`),
|
|
674
|
+
msgLengthAll: db.prepare(`
|
|
675
|
+
SELECT
|
|
676
|
+
CASE
|
|
677
|
+
WHEN len < 100 THEN '<100'
|
|
678
|
+
WHEN len BETWEEN 100 AND 499 THEN '100-499'
|
|
679
|
+
WHEN len BETWEEN 500 AND 999 THEN '500-999'
|
|
680
|
+
WHEN len BETWEEN 1000 AND 4999 THEN '1k-5k'
|
|
681
|
+
ELSE '5k+'
|
|
682
|
+
END AS bucket,
|
|
683
|
+
COUNT(*) AS count,
|
|
684
|
+
CAST(AVG(len) AS INTEGER) AS avgChars
|
|
685
|
+
FROM (
|
|
686
|
+
SELECT LENGTH(json_extract(content, '$.text')) AS len
|
|
687
|
+
FROM messages
|
|
688
|
+
WHERE role = 'user' AND json_extract(content, '$.text') IS NOT NULL
|
|
689
|
+
)
|
|
690
|
+
WHERE len > 0
|
|
691
|
+
GROUP BY bucket
|
|
692
|
+
ORDER BY MIN(len)
|
|
693
|
+
`),
|
|
694
|
+
msgLengthByProject: db.prepare(`
|
|
695
|
+
SELECT
|
|
696
|
+
CASE
|
|
697
|
+
WHEN len < 100 THEN '<100'
|
|
698
|
+
WHEN len BETWEEN 100 AND 499 THEN '100-499'
|
|
699
|
+
WHEN len BETWEEN 500 AND 999 THEN '500-999'
|
|
700
|
+
WHEN len BETWEEN 1000 AND 4999 THEN '1k-5k'
|
|
701
|
+
ELSE '5k+'
|
|
702
|
+
END AS bucket,
|
|
703
|
+
COUNT(*) AS count,
|
|
704
|
+
CAST(AVG(len) AS INTEGER) AS avgChars
|
|
705
|
+
FROM (
|
|
706
|
+
SELECT LENGTH(json_extract(m.content, '$.text')) AS len
|
|
707
|
+
FROM messages m
|
|
708
|
+
JOIN sessions s ON m.session_id = s.id
|
|
709
|
+
WHERE m.role = 'user' AND s.project_path = ? AND json_extract(m.content, '$.text') IS NOT NULL
|
|
710
|
+
)
|
|
711
|
+
WHERE len > 0
|
|
712
|
+
GROUP BY bucket
|
|
713
|
+
ORDER BY MIN(len)
|
|
714
|
+
`),
|
|
715
|
+
topBashCommandsAll: db.prepare(`
|
|
716
|
+
SELECT
|
|
717
|
+
SUBSTR(json_extract(content, '$.input.command'), 1, 80) AS command,
|
|
718
|
+
COUNT(*) AS count
|
|
719
|
+
FROM messages
|
|
720
|
+
WHERE role = 'tool' AND json_extract(content, '$.name') = 'Bash'
|
|
721
|
+
AND json_extract(content, '$.input.command') IS NOT NULL
|
|
722
|
+
GROUP BY SUBSTR(json_extract(content, '$.input.command'), 1, 80)
|
|
723
|
+
ORDER BY count DESC
|
|
724
|
+
LIMIT 10
|
|
725
|
+
`),
|
|
726
|
+
topBashCommandsByProject: db.prepare(`
|
|
727
|
+
SELECT
|
|
728
|
+
SUBSTR(json_extract(m.content, '$.input.command'), 1, 80) AS command,
|
|
729
|
+
COUNT(*) AS count
|
|
730
|
+
FROM messages m
|
|
731
|
+
JOIN sessions s ON m.session_id = s.id
|
|
732
|
+
WHERE m.role = 'tool' AND s.project_path = ? AND json_extract(m.content, '$.name') = 'Bash'
|
|
733
|
+
AND json_extract(m.content, '$.input.command') IS NOT NULL
|
|
734
|
+
GROUP BY SUBSTR(json_extract(m.content, '$.input.command'), 1, 80)
|
|
735
|
+
ORDER BY count DESC
|
|
736
|
+
LIMIT 10
|
|
737
|
+
`),
|
|
738
|
+
topFilesAll: db.prepare(`
|
|
739
|
+
SELECT
|
|
740
|
+
json_extract(content, '$.input.file_path') AS path,
|
|
741
|
+
COUNT(*) AS count,
|
|
742
|
+
json_extract(content, '$.name') AS tool
|
|
743
|
+
FROM messages
|
|
744
|
+
WHERE role = 'tool'
|
|
745
|
+
AND json_extract(content, '$.name') IN ('Read', 'Write', 'Edit')
|
|
746
|
+
AND json_extract(content, '$.input.file_path') IS NOT NULL
|
|
747
|
+
GROUP BY json_extract(content, '$.input.file_path'), json_extract(content, '$.name')
|
|
748
|
+
ORDER BY count DESC
|
|
749
|
+
LIMIT 15
|
|
750
|
+
`),
|
|
751
|
+
topFilesByProject: db.prepare(`
|
|
752
|
+
SELECT
|
|
753
|
+
json_extract(m.content, '$.input.file_path') AS path,
|
|
754
|
+
COUNT(*) AS count,
|
|
755
|
+
json_extract(m.content, '$.name') AS tool
|
|
756
|
+
FROM messages m
|
|
757
|
+
JOIN sessions s ON m.session_id = s.id
|
|
758
|
+
WHERE m.role = 'tool' AND s.project_path = ?
|
|
759
|
+
AND json_extract(m.content, '$.name') IN ('Read', 'Write', 'Edit')
|
|
760
|
+
AND json_extract(m.content, '$.input.file_path') IS NOT NULL
|
|
761
|
+
GROUP BY json_extract(m.content, '$.input.file_path'), json_extract(m.content, '$.name')
|
|
762
|
+
ORDER BY count DESC
|
|
763
|
+
LIMIT 15
|
|
764
|
+
`),
|
|
765
|
+
|
|
766
|
+
// ── Error pattern analytics ──────────────────────────────────
|
|
767
|
+
errorCategoriesAll: db.prepare(`
|
|
768
|
+
SELECT ${ERROR_CATEGORY_CASE} AS category, COUNT(*) AS count
|
|
769
|
+
FROM messages tr
|
|
770
|
+
WHERE tr.role = 'tool_result' AND json_extract(tr.content, '$.isError') = 1
|
|
771
|
+
GROUP BY category
|
|
772
|
+
ORDER BY count DESC
|
|
773
|
+
`),
|
|
774
|
+
errorCategoriesByProject: db.prepare(`
|
|
775
|
+
SELECT ${ERROR_CATEGORY_CASE} AS category, COUNT(*) AS count
|
|
776
|
+
FROM messages tr
|
|
777
|
+
JOIN sessions s ON tr.session_id = s.id
|
|
778
|
+
WHERE tr.role = 'tool_result' AND json_extract(tr.content, '$.isError') = 1
|
|
779
|
+
AND s.project_path = ?
|
|
780
|
+
GROUP BY category
|
|
781
|
+
ORDER BY count DESC
|
|
782
|
+
`),
|
|
783
|
+
errorTimelineAll: db.prepare(`
|
|
784
|
+
SELECT date(tr.created_at, 'unixepoch') AS date, COUNT(*) AS errors
|
|
785
|
+
FROM messages tr
|
|
786
|
+
WHERE tr.role = 'tool_result' AND json_extract(tr.content, '$.isError') = 1
|
|
787
|
+
AND tr.created_at >= unixepoch() - 30 * 86400
|
|
788
|
+
GROUP BY date(tr.created_at, 'unixepoch')
|
|
789
|
+
ORDER BY date ASC
|
|
790
|
+
`),
|
|
791
|
+
errorTimelineByProject: db.prepare(`
|
|
792
|
+
SELECT date(tr.created_at, 'unixepoch') AS date, COUNT(*) AS errors
|
|
793
|
+
FROM messages tr
|
|
794
|
+
JOIN sessions s ON tr.session_id = s.id
|
|
795
|
+
WHERE tr.role = 'tool_result' AND json_extract(tr.content, '$.isError') = 1
|
|
796
|
+
AND s.project_path = ? AND tr.created_at >= unixepoch() - 30 * 86400
|
|
797
|
+
GROUP BY date(tr.created_at, 'unixepoch')
|
|
798
|
+
ORDER BY date ASC
|
|
799
|
+
`),
|
|
800
|
+
errorsByToolAll: db.prepare(`
|
|
801
|
+
SELECT
|
|
802
|
+
COALESCE(json_extract(t.content, '$.name'), 'Unknown') AS tool,
|
|
803
|
+
${ERROR_CATEGORY_CASE} AS category,
|
|
804
|
+
COUNT(*) AS errors
|
|
805
|
+
FROM messages tr
|
|
806
|
+
LEFT JOIN messages t ON t.session_id = tr.session_id
|
|
807
|
+
AND t.role = 'tool'
|
|
808
|
+
AND json_extract(t.content, '$.id') = json_extract(tr.content, '$.toolUseId')
|
|
809
|
+
WHERE tr.role = 'tool_result' AND json_extract(tr.content, '$.isError') = 1
|
|
810
|
+
GROUP BY tool, category
|
|
811
|
+
ORDER BY errors DESC
|
|
812
|
+
`),
|
|
813
|
+
errorsByToolByProject: db.prepare(`
|
|
814
|
+
SELECT
|
|
815
|
+
COALESCE(json_extract(t.content, '$.name'), 'Unknown') AS tool,
|
|
816
|
+
${ERROR_CATEGORY_CASE} AS category,
|
|
817
|
+
COUNT(*) AS errors
|
|
818
|
+
FROM messages tr
|
|
819
|
+
JOIN sessions s ON tr.session_id = s.id
|
|
820
|
+
LEFT JOIN messages t ON t.session_id = tr.session_id
|
|
821
|
+
AND t.role = 'tool'
|
|
822
|
+
AND json_extract(t.content, '$.id') = json_extract(tr.content, '$.toolUseId')
|
|
823
|
+
WHERE tr.role = 'tool_result' AND json_extract(tr.content, '$.isError') = 1
|
|
824
|
+
AND s.project_path = ?
|
|
825
|
+
GROUP BY tool, category
|
|
826
|
+
ORDER BY errors DESC
|
|
827
|
+
`),
|
|
828
|
+
recentErrorsAll: db.prepare(`
|
|
829
|
+
SELECT
|
|
830
|
+
COALESCE(json_extract(t.content, '$.name'), 'Unknown') AS tool,
|
|
831
|
+
SUBSTR(json_extract(tr.content, '$.content'), 1, 200) AS preview,
|
|
832
|
+
json_extract(tr.content, '$.content') AS full_content,
|
|
833
|
+
s.title AS session_title,
|
|
834
|
+
tr.created_at AS timestamp
|
|
835
|
+
FROM messages tr
|
|
836
|
+
JOIN sessions s ON tr.session_id = s.id
|
|
837
|
+
LEFT JOIN messages t ON t.session_id = tr.session_id
|
|
838
|
+
AND t.role = 'tool'
|
|
839
|
+
AND json_extract(t.content, '$.id') = json_extract(tr.content, '$.toolUseId')
|
|
840
|
+
WHERE tr.role = 'tool_result' AND json_extract(tr.content, '$.isError') = 1
|
|
841
|
+
ORDER BY tr.created_at DESC
|
|
842
|
+
LIMIT 20
|
|
843
|
+
`),
|
|
844
|
+
recentErrorsByProject: db.prepare(`
|
|
845
|
+
SELECT
|
|
846
|
+
COALESCE(json_extract(t.content, '$.name'), 'Unknown') AS tool,
|
|
847
|
+
SUBSTR(json_extract(tr.content, '$.content'), 1, 200) AS preview,
|
|
848
|
+
json_extract(tr.content, '$.content') AS full_content,
|
|
849
|
+
s.title AS session_title,
|
|
850
|
+
tr.created_at AS timestamp
|
|
851
|
+
FROM messages tr
|
|
852
|
+
JOIN sessions s ON tr.session_id = s.id
|
|
853
|
+
LEFT JOIN messages t ON t.session_id = tr.session_id
|
|
854
|
+
AND t.role = 'tool'
|
|
855
|
+
AND json_extract(t.content, '$.id') = json_extract(tr.content, '$.toolUseId')
|
|
856
|
+
WHERE tr.role = 'tool_result' AND json_extract(tr.content, '$.isError') = 1
|
|
857
|
+
AND s.project_path = ?
|
|
858
|
+
ORDER BY tr.created_at DESC
|
|
859
|
+
LIMIT 20
|
|
860
|
+
`),
|
|
861
|
+
|
|
862
|
+
// ── Model usage & cache efficiency ─────────────────────────
|
|
863
|
+
modelUsageAll: db.prepare(`
|
|
864
|
+
SELECT
|
|
865
|
+
COALESCE(model, 'unknown') AS model,
|
|
866
|
+
COUNT(*) AS count,
|
|
867
|
+
COALESCE(SUM(cost_usd), 0) AS cost,
|
|
868
|
+
COALESCE(SUM(input_tokens + output_tokens), 0) AS tokens
|
|
869
|
+
FROM costs
|
|
870
|
+
GROUP BY COALESCE(model, 'unknown')
|
|
871
|
+
ORDER BY cost DESC
|
|
872
|
+
`),
|
|
873
|
+
modelUsageByProject: db.prepare(`
|
|
874
|
+
SELECT
|
|
875
|
+
COALESCE(c.model, 'unknown') AS model,
|
|
876
|
+
COUNT(*) AS count,
|
|
877
|
+
COALESCE(SUM(c.cost_usd), 0) AS cost,
|
|
878
|
+
COALESCE(SUM(c.input_tokens + c.output_tokens), 0) AS tokens
|
|
879
|
+
FROM costs c
|
|
880
|
+
JOIN sessions s ON c.session_id = s.id
|
|
881
|
+
WHERE s.project_path = ?
|
|
882
|
+
GROUP BY COALESCE(c.model, 'unknown')
|
|
883
|
+
ORDER BY cost DESC
|
|
884
|
+
`),
|
|
885
|
+
cacheEfficiencyAll: db.prepare(`
|
|
886
|
+
SELECT
|
|
887
|
+
date(c.created_at, 'unixepoch') AS date,
|
|
888
|
+
COALESCE(SUM(c.cache_read_tokens), 0) AS cache_read,
|
|
889
|
+
COALESCE(SUM(c.cache_creation_tokens), 0) AS cache_creation,
|
|
890
|
+
COALESCE(SUM(c.input_tokens), 0) AS total_input
|
|
891
|
+
FROM costs c
|
|
892
|
+
WHERE c.created_at >= unixepoch() - 30 * 86400
|
|
893
|
+
GROUP BY date(c.created_at, 'unixepoch')
|
|
894
|
+
ORDER BY date ASC
|
|
895
|
+
`),
|
|
896
|
+
cacheEfficiencyByProject: db.prepare(`
|
|
897
|
+
SELECT
|
|
898
|
+
date(c.created_at, 'unixepoch') AS date,
|
|
899
|
+
COALESCE(SUM(c.cache_read_tokens), 0) AS cache_read,
|
|
900
|
+
COALESCE(SUM(c.cache_creation_tokens), 0) AS cache_creation,
|
|
901
|
+
COALESCE(SUM(c.input_tokens), 0) AS total_input
|
|
902
|
+
FROM costs c
|
|
903
|
+
JOIN sessions s ON c.session_id = s.id
|
|
904
|
+
WHERE s.project_path = ? AND c.created_at >= unixepoch() - 30 * 86400
|
|
905
|
+
GROUP BY date(c.created_at, 'unixepoch')
|
|
906
|
+
ORDER BY date ASC
|
|
907
|
+
`),
|
|
908
|
+
};
|
|
909
|
+
|
|
910
|
+
export function getAnalyticsOverview(projectPath) {
|
|
911
|
+
const overview = projectPath
|
|
912
|
+
? analyticsStmts.overviewByProject.get(projectPath)
|
|
913
|
+
: analyticsStmts.overviewAll.get();
|
|
914
|
+
const errors = projectPath
|
|
915
|
+
? analyticsStmts.errorRateByProject.get(projectPath)
|
|
916
|
+
: analyticsStmts.errorRateAll.get();
|
|
917
|
+
return {
|
|
918
|
+
...overview,
|
|
919
|
+
errorRate: errors.total > 0 ? (errors.errors / errors.total * 100) : 0,
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
export function getDailyBreakdown(projectPath) {
|
|
924
|
+
return projectPath
|
|
925
|
+
? analyticsStmts.dailyBreakdownByProject.all(projectPath)
|
|
926
|
+
: analyticsStmts.dailyBreakdownAll.all();
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
export function getHourlyActivity(projectPath) {
|
|
930
|
+
return projectPath
|
|
931
|
+
? analyticsStmts.hourlyActivityByProject.all(projectPath)
|
|
932
|
+
: analyticsStmts.hourlyActivityAll.all();
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
export function getProjectBreakdown() {
|
|
936
|
+
return analyticsStmts.projectBreakdown.all();
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
export function getTopSessionsByCost(projectPath) {
|
|
940
|
+
return projectPath
|
|
941
|
+
? analyticsStmts.topSessionsByProject.all(projectPath)
|
|
942
|
+
: analyticsStmts.topSessionsAll.all();
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
export function getToolUsage(projectPath) {
|
|
946
|
+
return projectPath
|
|
947
|
+
? analyticsStmts.toolUsageByProject.all(projectPath)
|
|
948
|
+
: analyticsStmts.toolUsageAll.all();
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
export function getToolErrors(projectPath) {
|
|
952
|
+
return projectPath
|
|
953
|
+
? analyticsStmts.toolErrorsByProject.all(projectPath)
|
|
954
|
+
: analyticsStmts.toolErrorsAll.all();
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
export function getSessionDepth(projectPath) {
|
|
958
|
+
return projectPath
|
|
959
|
+
? analyticsStmts.sessionDepthByProject.all(projectPath)
|
|
960
|
+
: analyticsStmts.sessionDepthAll.all();
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
export function getMsgLengthDistribution(projectPath) {
|
|
964
|
+
return projectPath
|
|
965
|
+
? analyticsStmts.msgLengthByProject.all(projectPath)
|
|
966
|
+
: analyticsStmts.msgLengthAll.all();
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
export function getTopBashCommands(projectPath) {
|
|
970
|
+
return projectPath
|
|
971
|
+
? analyticsStmts.topBashCommandsByProject.all(projectPath)
|
|
972
|
+
: analyticsStmts.topBashCommandsAll.all();
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
export function getTopFiles(projectPath) {
|
|
976
|
+
return projectPath
|
|
977
|
+
? analyticsStmts.topFilesByProject.all(projectPath)
|
|
978
|
+
: analyticsStmts.topFilesAll.all();
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
export function getErrorCategories(projectPath) {
|
|
982
|
+
return projectPath
|
|
983
|
+
? analyticsStmts.errorCategoriesByProject.all(projectPath)
|
|
984
|
+
: analyticsStmts.errorCategoriesAll.all();
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
export function getErrorTimeline(projectPath) {
|
|
988
|
+
return projectPath
|
|
989
|
+
? analyticsStmts.errorTimelineByProject.all(projectPath)
|
|
990
|
+
: analyticsStmts.errorTimelineAll.all();
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
export function getErrorsByTool(projectPath) {
|
|
994
|
+
return projectPath
|
|
995
|
+
? analyticsStmts.errorsByToolByProject.all(projectPath)
|
|
996
|
+
: analyticsStmts.errorsByToolAll.all();
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
export function getRecentErrors(projectPath) {
|
|
1000
|
+
return projectPath
|
|
1001
|
+
? analyticsStmts.recentErrorsByProject.all(projectPath)
|
|
1002
|
+
: analyticsStmts.recentErrorsAll.all();
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
export function getModelUsage(projectPath) {
|
|
1006
|
+
return projectPath
|
|
1007
|
+
? analyticsStmts.modelUsageByProject.all(projectPath)
|
|
1008
|
+
: analyticsStmts.modelUsageAll.all();
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
export function getYearlyActivity() {
|
|
1012
|
+
return stmts.yearlyActivity.all();
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
export function getCacheEfficiency(projectPath) {
|
|
1016
|
+
return projectPath
|
|
1017
|
+
? analyticsStmts.cacheEfficiencyByProject.all(projectPath)
|
|
1018
|
+
: analyticsStmts.cacheEfficiencyAll.all();
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
// ── Todo CRUD ────────────────────────────────────────────────
|
|
1022
|
+
export function listTodos(archived = false) {
|
|
1023
|
+
return archived ? stmts.listArchivedTodos.all() : stmts.listTodos.all();
|
|
1024
|
+
}
|
|
1025
|
+
export function createTodo(text) { return stmts.createTodo.run(text); }
|
|
1026
|
+
export function updateTodo(id, text, done, priority) { return stmts.updateTodo.run(text, done, priority, id); }
|
|
1027
|
+
export function archiveTodo(id, archived) { return stmts.archiveTodo.run(archived ? 1 : 0, id); }
|
|
1028
|
+
export function deleteTodo(id) { return stmts.deleteTodo.run(id); }
|
|
1029
|
+
|
|
1030
|
+
export function getTodoCounts() { return stmts.todoCounts.get(); }
|
|
1031
|
+
|
|
1032
|
+
// ── Brag CRUD ─────────────────────────────────────────────────
|
|
1033
|
+
export function createBrag(todoId, text, summary) { return stmts.createBrag.run(todoId, text, summary); }
|
|
1034
|
+
export function listBrags() { return stmts.listBrags.all(); }
|
|
1035
|
+
export function deleteBrag(id) { return stmts.deleteBrag.run(id); }
|
|
1036
|
+
|
|
1037
|
+
// ── Push subscription queries ────────────────────────────────
|
|
1038
|
+
const pushStmts = {
|
|
1039
|
+
upsert: db.prepare(
|
|
1040
|
+
`INSERT INTO push_subscriptions (endpoint, keys_p256dh, keys_auth)
|
|
1041
|
+
VALUES (?, ?, ?)
|
|
1042
|
+
ON CONFLICT(endpoint) DO UPDATE SET keys_p256dh = excluded.keys_p256dh, keys_auth = excluded.keys_auth`
|
|
1043
|
+
),
|
|
1044
|
+
delete: db.prepare(`DELETE FROM push_subscriptions WHERE endpoint = ?`),
|
|
1045
|
+
getAll: db.prepare(`SELECT * FROM push_subscriptions`),
|
|
1046
|
+
};
|
|
1047
|
+
|
|
1048
|
+
export function upsertPushSubscription(endpoint, p256dh, auth) {
|
|
1049
|
+
pushStmts.upsert.run(endpoint, p256dh, auth);
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
export function deletePushSubscription(endpoint) {
|
|
1053
|
+
pushStmts.delete.run(endpoint);
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
export function getAllPushSubscriptions() {
|
|
1057
|
+
return pushStmts.getAll.all();
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
// ── Agent context (shared memory) ─────────────────────────
|
|
1061
|
+
const ctxStmts = {
|
|
1062
|
+
set: db.prepare(
|
|
1063
|
+
`INSERT INTO agent_context (run_id, agent_id, key, value)
|
|
1064
|
+
VALUES (?, ?, ?, ?)
|
|
1065
|
+
ON CONFLICT(run_id, agent_id, key) DO UPDATE SET value = excluded.value`
|
|
1066
|
+
),
|
|
1067
|
+
get: db.prepare(
|
|
1068
|
+
`SELECT value FROM agent_context WHERE run_id = ? AND agent_id = ? AND key = ?`
|
|
1069
|
+
),
|
|
1070
|
+
getAllForRun: db.prepare(
|
|
1071
|
+
`SELECT agent_id, key, value, created_at FROM agent_context WHERE run_id = ? ORDER BY created_at ASC`
|
|
1072
|
+
),
|
|
1073
|
+
getByKey: db.prepare(
|
|
1074
|
+
`SELECT agent_id, value FROM agent_context WHERE run_id = ? AND key = ?`
|
|
1075
|
+
),
|
|
1076
|
+
deleteRun: db.prepare(
|
|
1077
|
+
`DELETE FROM agent_context WHERE run_id = ?`
|
|
1078
|
+
),
|
|
1079
|
+
};
|
|
1080
|
+
|
|
1081
|
+
export function setAgentContext(runId, agentId, key, value) {
|
|
1082
|
+
ctxStmts.set.run(runId, agentId, key, typeof value === "string" ? value : JSON.stringify(value));
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
export function getAgentContext(runId, agentId, key) {
|
|
1086
|
+
const row = ctxStmts.get.get(runId, agentId, key);
|
|
1087
|
+
return row ? row.value : null;
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
export function getAllAgentContext(runId) {
|
|
1091
|
+
return ctxStmts.getAllForRun.all(runId);
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
export function getAgentContextByKey(runId, key) {
|
|
1095
|
+
return ctxStmts.getByKey.all(runId, key);
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
export function deleteAgentContext(runId) {
|
|
1099
|
+
ctxStmts.deleteRun.run(runId);
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
// ── Agent runs (monitoring) ────────────────────────────
|
|
1103
|
+
const runStmts = {
|
|
1104
|
+
insert: db.prepare(
|
|
1105
|
+
`INSERT INTO agent_runs (run_id, agent_id, agent_title, run_type, parent_id, status)
|
|
1106
|
+
VALUES (?, ?, ?, ?, ?, 'running')`
|
|
1107
|
+
),
|
|
1108
|
+
complete: db.prepare(
|
|
1109
|
+
`UPDATE agent_runs SET status = ?, turns = ?, cost_usd = ?, duration_ms = ?,
|
|
1110
|
+
input_tokens = ?, output_tokens = ?, error = ?, completed_at = unixepoch()
|
|
1111
|
+
WHERE run_id = ? AND agent_id = ?`
|
|
1112
|
+
),
|
|
1113
|
+
listRecent: db.prepare(
|
|
1114
|
+
`SELECT * FROM agent_runs ORDER BY started_at DESC LIMIT ?`
|
|
1115
|
+
),
|
|
1116
|
+
agentSummary: db.prepare(
|
|
1117
|
+
`SELECT
|
|
1118
|
+
agent_id, agent_title,
|
|
1119
|
+
COUNT(*) AS runs,
|
|
1120
|
+
SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) AS successes,
|
|
1121
|
+
SUM(CASE WHEN status = 'error' THEN 1 ELSE 0 END) AS errors,
|
|
1122
|
+
COALESCE(SUM(cost_usd), 0) AS total_cost,
|
|
1123
|
+
COALESCE(AVG(CASE WHEN status = 'completed' THEN cost_usd END), 0) AS avg_cost,
|
|
1124
|
+
COALESCE(AVG(CASE WHEN status = 'completed' THEN duration_ms END), 0) AS avg_duration,
|
|
1125
|
+
COALESCE(AVG(CASE WHEN status = 'completed' THEN turns END), 0) AS avg_turns,
|
|
1126
|
+
COALESCE(SUM(input_tokens), 0) AS total_input_tokens,
|
|
1127
|
+
COALESCE(SUM(output_tokens), 0) AS total_output_tokens
|
|
1128
|
+
FROM agent_runs
|
|
1129
|
+
GROUP BY agent_id
|
|
1130
|
+
ORDER BY total_cost DESC`
|
|
1131
|
+
),
|
|
1132
|
+
overview: db.prepare(
|
|
1133
|
+
`SELECT
|
|
1134
|
+
COUNT(*) AS total_runs,
|
|
1135
|
+
SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) AS completed,
|
|
1136
|
+
SUM(CASE WHEN status = 'error' THEN 1 ELSE 0 END) AS errored,
|
|
1137
|
+
COALESCE(SUM(cost_usd), 0) AS total_cost,
|
|
1138
|
+
COALESCE(AVG(CASE WHEN status = 'completed' THEN duration_ms END), 0) AS avg_duration,
|
|
1139
|
+
COALESCE(AVG(CASE WHEN status = 'completed' THEN turns END), 0) AS avg_turns,
|
|
1140
|
+
COALESCE(SUM(input_tokens), 0) AS total_input_tokens,
|
|
1141
|
+
COALESCE(SUM(output_tokens), 0) AS total_output_tokens
|
|
1142
|
+
FROM agent_runs`
|
|
1143
|
+
),
|
|
1144
|
+
byType: db.prepare(
|
|
1145
|
+
`SELECT
|
|
1146
|
+
run_type,
|
|
1147
|
+
COUNT(*) AS runs,
|
|
1148
|
+
COALESCE(SUM(cost_usd), 0) AS cost,
|
|
1149
|
+
COALESCE(AVG(duration_ms), 0) AS avg_duration
|
|
1150
|
+
FROM agent_runs
|
|
1151
|
+
GROUP BY run_type
|
|
1152
|
+
ORDER BY runs DESC`
|
|
1153
|
+
),
|
|
1154
|
+
dailyRuns: db.prepare(
|
|
1155
|
+
`SELECT
|
|
1156
|
+
date(started_at, 'unixepoch') AS date,
|
|
1157
|
+
COUNT(*) AS runs,
|
|
1158
|
+
SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) AS completed,
|
|
1159
|
+
SUM(CASE WHEN status = 'error' THEN 1 ELSE 0 END) AS errored,
|
|
1160
|
+
COALESCE(SUM(cost_usd), 0) AS cost
|
|
1161
|
+
FROM agent_runs
|
|
1162
|
+
WHERE started_at >= unixepoch() - 30 * 86400
|
|
1163
|
+
GROUP BY date(started_at, 'unixepoch')
|
|
1164
|
+
ORDER BY date ASC`
|
|
1165
|
+
),
|
|
1166
|
+
};
|
|
1167
|
+
|
|
1168
|
+
export function recordAgentRunStart(runId, agentId, agentTitle, runType = 'single', parentId = null) {
|
|
1169
|
+
runStmts.insert.run(runId, agentId, agentTitle, runType, parentId);
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
export function recordAgentRunComplete(runId, agentId, status, turns, costUsd, durationMs, inputTokens, outputTokens, error = null) {
|
|
1173
|
+
runStmts.complete.run(status, turns, costUsd, durationMs, inputTokens, outputTokens, error, runId, agentId);
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
export function getAgentRunsRecent(limit = 50) {
|
|
1177
|
+
return runStmts.listRecent.all(limit);
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
export function getAgentRunsSummary() {
|
|
1181
|
+
return runStmts.agentSummary.all();
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
export function getAgentRunsOverview() {
|
|
1185
|
+
return runStmts.overview.get();
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
export function getAgentRunsByType() {
|
|
1189
|
+
return runStmts.byType.all();
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
export function getAgentRunsDaily() {
|
|
1193
|
+
return runStmts.dailyRuns.all();
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
export function getDb() {
|
|
1197
|
+
return db;
|
|
1198
|
+
}
|