@code-insights/cli 2.1.0 → 3.0.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 (271) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +146 -156
  3. package/dashboard-dist/assets/index-BMhL7wL8.css +1 -0
  4. package/dashboard-dist/assets/index-CuCBzQyQ.js +548 -0
  5. package/dashboard-dist/dist/assets/index-BMhL7wL8.css +1 -0
  6. package/dashboard-dist/dist/assets/index-CUWKxcRo.js +548 -0
  7. package/dashboard-dist/dist/assets/index-CuCBzQyQ.js +548 -0
  8. package/dashboard-dist/dist/favicon.svg +4 -0
  9. package/dashboard-dist/dist/index.html +33 -0
  10. package/dashboard-dist/favicon.svg +4 -0
  11. package/dashboard-dist/index.html +33 -0
  12. package/dist/commands/config.d.ts.map +1 -1
  13. package/dist/commands/config.js +188 -69
  14. package/dist/commands/config.js.map +1 -1
  15. package/dist/commands/dashboard.d.ts +16 -0
  16. package/dist/commands/dashboard.d.ts.map +1 -0
  17. package/dist/commands/dashboard.js +82 -0
  18. package/dist/commands/dashboard.js.map +1 -0
  19. package/dist/commands/init.d.ts +3 -4
  20. package/dist/commands/init.d.ts.map +1 -1
  21. package/dist/commands/init.js +24 -201
  22. package/dist/commands/init.js.map +1 -1
  23. package/dist/commands/install-hook.d.ts.map +1 -1
  24. package/dist/commands/install-hook.js +4 -21
  25. package/dist/commands/install-hook.js.map +1 -1
  26. package/dist/commands/open.d.ts +2 -1
  27. package/dist/commands/open.d.ts.map +1 -1
  28. package/dist/commands/open.js +10 -21
  29. package/dist/commands/open.js.map +1 -1
  30. package/dist/commands/reset.d.ts.map +1 -1
  31. package/dist/commands/reset.js +38 -69
  32. package/dist/commands/reset.js.map +1 -1
  33. package/dist/commands/stats/actions/error-handler.d.ts.map +1 -1
  34. package/dist/commands/stats/actions/error-handler.js +1 -11
  35. package/dist/commands/stats/actions/error-handler.js.map +1 -1
  36. package/dist/commands/stats/data/local.d.ts +0 -2
  37. package/dist/commands/stats/data/local.d.ts.map +1 -1
  38. package/dist/commands/stats/data/local.js +44 -44
  39. package/dist/commands/stats/data/local.js.map +1 -1
  40. package/dist/commands/stats/data/source.d.ts +3 -9
  41. package/dist/commands/stats/data/source.d.ts.map +1 -1
  42. package/dist/commands/stats/data/source.js +3 -35
  43. package/dist/commands/stats/data/source.js.map +1 -1
  44. package/dist/commands/stats/data/types.d.ts +1 -12
  45. package/dist/commands/stats/data/types.d.ts.map +1 -1
  46. package/dist/commands/stats/data/types.js +0 -16
  47. package/dist/commands/stats/data/types.js.map +1 -1
  48. package/dist/commands/stats/shared.d.ts.map +1 -1
  49. package/dist/commands/stats/shared.js +1 -5
  50. package/dist/commands/stats/shared.js.map +1 -1
  51. package/dist/commands/status.d.ts.map +1 -1
  52. package/dist/commands/status.js +53 -92
  53. package/dist/commands/status.js.map +1 -1
  54. package/dist/commands/sync.d.ts +5 -5
  55. package/dist/commands/sync.d.ts.map +1 -1
  56. package/dist/commands/sync.js +31 -39
  57. package/dist/commands/sync.js.map +1 -1
  58. package/dist/commands/telemetry.d.ts.map +1 -1
  59. package/dist/commands/telemetry.js +0 -1
  60. package/dist/commands/telemetry.js.map +1 -1
  61. package/dist/constants/llm-providers.d.ts +5 -0
  62. package/dist/constants/llm-providers.d.ts.map +1 -0
  63. package/dist/constants/llm-providers.js +56 -0
  64. package/dist/constants/llm-providers.js.map +1 -0
  65. package/dist/db/client.d.ts +17 -0
  66. package/dist/db/client.d.ts.map +1 -0
  67. package/dist/db/client.js +53 -0
  68. package/dist/db/client.js.map +1 -0
  69. package/dist/db/migrate.d.ts +9 -0
  70. package/dist/db/migrate.d.ts.map +1 -0
  71. package/dist/db/migrate.js +31 -0
  72. package/dist/db/migrate.js.map +1 -0
  73. package/dist/db/read.d.ts +42 -0
  74. package/dist/db/read.d.ts.map +1 -0
  75. package/dist/db/read.js +194 -0
  76. package/dist/db/read.js.map +1 -0
  77. package/dist/db/schema.d.ts +3 -0
  78. package/dist/db/schema.d.ts.map +1 -0
  79. package/dist/db/schema.js +129 -0
  80. package/dist/db/schema.js.map +1 -0
  81. package/dist/db/write.d.ts +24 -0
  82. package/dist/db/write.d.ts.map +1 -0
  83. package/dist/db/write.js +287 -0
  84. package/dist/db/write.js.map +1 -0
  85. package/dist/firebase/client.d.ts +5 -0
  86. package/dist/firebase/client.d.ts.map +1 -1
  87. package/dist/firebase/client.js +23 -0
  88. package/dist/firebase/client.js.map +1 -1
  89. package/dist/index.js +12 -12
  90. package/dist/index.js.map +1 -1
  91. package/dist/providers/context.d.ts +3 -0
  92. package/dist/providers/context.d.ts.map +1 -0
  93. package/dist/providers/context.js +13 -0
  94. package/dist/providers/context.js.map +1 -0
  95. package/dist/providers/copilot.d.ts +18 -0
  96. package/dist/providers/copilot.d.ts.map +1 -0
  97. package/dist/providers/copilot.js +289 -0
  98. package/dist/providers/copilot.js.map +1 -0
  99. package/dist/providers/cursor.d.ts.map +1 -1
  100. package/dist/providers/cursor.js +8 -3
  101. package/dist/providers/cursor.js.map +1 -1
  102. package/dist/providers/registry.d.ts.map +1 -1
  103. package/dist/providers/registry.js +3 -0
  104. package/dist/providers/registry.js.map +1 -1
  105. package/dist/types.d.ts +38 -47
  106. package/dist/types.d.ts.map +1 -1
  107. package/dist/utils/browser.d.ts +6 -0
  108. package/dist/utils/browser.d.ts.map +1 -0
  109. package/dist/utils/browser.js +23 -0
  110. package/dist/utils/browser.js.map +1 -0
  111. package/dist/utils/config.d.ts +9 -21
  112. package/dist/utils/config.d.ts.map +1 -1
  113. package/dist/utils/config.js +23 -50
  114. package/dist/utils/config.js.map +1 -1
  115. package/dist/utils/telemetry.js +2 -7
  116. package/dist/utils/telemetry.js.map +1 -1
  117. package/dist/utils/tips.js +1 -1
  118. package/dist/utils/tips.js.map +1 -1
  119. package/package.json +19 -5
  120. package/server-dist/dist/index.d.ts +12 -0
  121. package/server-dist/dist/index.d.ts.map +1 -0
  122. package/server-dist/dist/index.js +92 -0
  123. package/server-dist/dist/index.js.map +1 -0
  124. package/server-dist/dist/llm/analysis.d.ts +80 -0
  125. package/server-dist/dist/llm/analysis.d.ts.map +1 -0
  126. package/server-dist/dist/llm/analysis.js +509 -0
  127. package/server-dist/dist/llm/analysis.js.map +1 -0
  128. package/server-dist/dist/llm/client.d.ts +27 -0
  129. package/server-dist/dist/llm/client.d.ts.map +1 -0
  130. package/server-dist/dist/llm/client.js +71 -0
  131. package/server-dist/dist/llm/client.js.map +1 -0
  132. package/server-dist/dist/llm/index.d.ts +7 -0
  133. package/server-dist/dist/llm/index.d.ts.map +1 -0
  134. package/server-dist/dist/llm/index.js +5 -0
  135. package/server-dist/dist/llm/index.js.map +1 -0
  136. package/server-dist/dist/llm/prompts.d.ts +73 -0
  137. package/server-dist/dist/llm/prompts.d.ts.map +1 -0
  138. package/server-dist/dist/llm/prompts.js +242 -0
  139. package/server-dist/dist/llm/prompts.js.map +1 -0
  140. package/server-dist/dist/llm/providers/anthropic.d.ts +3 -0
  141. package/server-dist/dist/llm/providers/anthropic.d.ts.map +1 -0
  142. package/server-dist/dist/llm/providers/anthropic.js +45 -0
  143. package/server-dist/dist/llm/providers/anthropic.js.map +1 -0
  144. package/server-dist/dist/llm/providers/gemini.d.ts +3 -0
  145. package/server-dist/dist/llm/providers/gemini.d.ts.map +1 -0
  146. package/server-dist/dist/llm/providers/gemini.js +51 -0
  147. package/server-dist/dist/llm/providers/gemini.js.map +1 -0
  148. package/server-dist/dist/llm/providers/ollama.d.ts +12 -0
  149. package/server-dist/dist/llm/providers/ollama.d.ts.map +1 -0
  150. package/server-dist/dist/llm/providers/ollama.js +61 -0
  151. package/server-dist/dist/llm/providers/ollama.js.map +1 -0
  152. package/server-dist/dist/llm/providers/openai.d.ts +3 -0
  153. package/server-dist/dist/llm/providers/openai.d.ts.map +1 -0
  154. package/server-dist/dist/llm/providers/openai.js +39 -0
  155. package/server-dist/dist/llm/providers/openai.js.map +1 -0
  156. package/server-dist/dist/llm/types.d.ts +22 -0
  157. package/server-dist/dist/llm/types.d.ts.map +1 -0
  158. package/server-dist/dist/llm/types.js +5 -0
  159. package/server-dist/dist/llm/types.js.map +1 -0
  160. package/server-dist/dist/routes/analysis.d.ts +4 -0
  161. package/server-dist/dist/routes/analysis.d.ts.map +1 -0
  162. package/server-dist/dist/routes/analysis.js +103 -0
  163. package/server-dist/dist/routes/analysis.js.map +1 -0
  164. package/server-dist/dist/routes/analytics.d.ts +4 -0
  165. package/server-dist/dist/routes/analytics.d.ts.map +1 -0
  166. package/server-dist/dist/routes/analytics.js +47 -0
  167. package/server-dist/dist/routes/analytics.js.map +1 -0
  168. package/server-dist/dist/routes/config.d.ts +4 -0
  169. package/server-dist/dist/routes/config.d.ts.map +1 -0
  170. package/server-dist/dist/routes/config.js +108 -0
  171. package/server-dist/dist/routes/config.js.map +1 -0
  172. package/server-dist/dist/routes/export.d.ts +4 -0
  173. package/server-dist/dist/routes/export.d.ts.map +1 -0
  174. package/server-dist/dist/routes/export.js +52 -0
  175. package/server-dist/dist/routes/export.js.map +1 -0
  176. package/server-dist/dist/routes/insights.d.ts +4 -0
  177. package/server-dist/dist/routes/insights.d.ts.map +1 -0
  178. package/server-dist/dist/routes/insights.js +80 -0
  179. package/server-dist/dist/routes/insights.js.map +1 -0
  180. package/server-dist/dist/routes/messages.d.ts +4 -0
  181. package/server-dist/dist/routes/messages.d.ts.map +1 -0
  182. package/server-dist/dist/routes/messages.js +19 -0
  183. package/server-dist/dist/routes/messages.js.map +1 -0
  184. package/server-dist/dist/routes/projects.d.ts +4 -0
  185. package/server-dist/dist/routes/projects.d.ts.map +1 -0
  186. package/server-dist/dist/routes/projects.js +32 -0
  187. package/server-dist/dist/routes/projects.js.map +1 -0
  188. package/server-dist/dist/routes/sessions.d.ts +4 -0
  189. package/server-dist/dist/routes/sessions.d.ts.map +1 -0
  190. package/server-dist/dist/routes/sessions.js +65 -0
  191. package/server-dist/dist/routes/sessions.js.map +1 -0
  192. package/server-dist/dist/utils.d.ts +6 -0
  193. package/server-dist/dist/utils.d.ts.map +1 -0
  194. package/server-dist/dist/utils.js +9 -0
  195. package/server-dist/dist/utils.js.map +1 -0
  196. package/server-dist/index.d.ts +12 -0
  197. package/server-dist/index.d.ts.map +1 -0
  198. package/server-dist/index.js +92 -0
  199. package/server-dist/index.js.map +1 -0
  200. package/server-dist/llm/analysis.d.ts +80 -0
  201. package/server-dist/llm/analysis.d.ts.map +1 -0
  202. package/server-dist/llm/analysis.js +509 -0
  203. package/server-dist/llm/analysis.js.map +1 -0
  204. package/server-dist/llm/client.d.ts +27 -0
  205. package/server-dist/llm/client.d.ts.map +1 -0
  206. package/server-dist/llm/client.js +71 -0
  207. package/server-dist/llm/client.js.map +1 -0
  208. package/server-dist/llm/index.d.ts +7 -0
  209. package/server-dist/llm/index.d.ts.map +1 -0
  210. package/server-dist/llm/index.js +5 -0
  211. package/server-dist/llm/index.js.map +1 -0
  212. package/server-dist/llm/prompts.d.ts +73 -0
  213. package/server-dist/llm/prompts.d.ts.map +1 -0
  214. package/server-dist/llm/prompts.js +242 -0
  215. package/server-dist/llm/prompts.js.map +1 -0
  216. package/server-dist/llm/providers/anthropic.d.ts +3 -0
  217. package/server-dist/llm/providers/anthropic.d.ts.map +1 -0
  218. package/server-dist/llm/providers/anthropic.js +45 -0
  219. package/server-dist/llm/providers/anthropic.js.map +1 -0
  220. package/server-dist/llm/providers/gemini.d.ts +3 -0
  221. package/server-dist/llm/providers/gemini.d.ts.map +1 -0
  222. package/server-dist/llm/providers/gemini.js +51 -0
  223. package/server-dist/llm/providers/gemini.js.map +1 -0
  224. package/server-dist/llm/providers/ollama.d.ts +12 -0
  225. package/server-dist/llm/providers/ollama.d.ts.map +1 -0
  226. package/server-dist/llm/providers/ollama.js +61 -0
  227. package/server-dist/llm/providers/ollama.js.map +1 -0
  228. package/server-dist/llm/providers/openai.d.ts +3 -0
  229. package/server-dist/llm/providers/openai.d.ts.map +1 -0
  230. package/server-dist/llm/providers/openai.js +39 -0
  231. package/server-dist/llm/providers/openai.js.map +1 -0
  232. package/server-dist/llm/types.d.ts +22 -0
  233. package/server-dist/llm/types.d.ts.map +1 -0
  234. package/server-dist/llm/types.js +5 -0
  235. package/server-dist/llm/types.js.map +1 -0
  236. package/server-dist/routes/analysis.d.ts +4 -0
  237. package/server-dist/routes/analysis.d.ts.map +1 -0
  238. package/server-dist/routes/analysis.js +103 -0
  239. package/server-dist/routes/analysis.js.map +1 -0
  240. package/server-dist/routes/analytics.d.ts +4 -0
  241. package/server-dist/routes/analytics.d.ts.map +1 -0
  242. package/server-dist/routes/analytics.js +47 -0
  243. package/server-dist/routes/analytics.js.map +1 -0
  244. package/server-dist/routes/config.d.ts +4 -0
  245. package/server-dist/routes/config.d.ts.map +1 -0
  246. package/server-dist/routes/config.js +108 -0
  247. package/server-dist/routes/config.js.map +1 -0
  248. package/server-dist/routes/export.d.ts +4 -0
  249. package/server-dist/routes/export.d.ts.map +1 -0
  250. package/server-dist/routes/export.js +52 -0
  251. package/server-dist/routes/export.js.map +1 -0
  252. package/server-dist/routes/insights.d.ts +4 -0
  253. package/server-dist/routes/insights.d.ts.map +1 -0
  254. package/server-dist/routes/insights.js +80 -0
  255. package/server-dist/routes/insights.js.map +1 -0
  256. package/server-dist/routes/messages.d.ts +4 -0
  257. package/server-dist/routes/messages.d.ts.map +1 -0
  258. package/server-dist/routes/messages.js +19 -0
  259. package/server-dist/routes/messages.js.map +1 -0
  260. package/server-dist/routes/projects.d.ts +4 -0
  261. package/server-dist/routes/projects.d.ts.map +1 -0
  262. package/server-dist/routes/projects.js +32 -0
  263. package/server-dist/routes/projects.js.map +1 -0
  264. package/server-dist/routes/sessions.d.ts +4 -0
  265. package/server-dist/routes/sessions.d.ts.map +1 -0
  266. package/server-dist/routes/sessions.js +65 -0
  267. package/server-dist/routes/sessions.js.map +1 -0
  268. package/server-dist/utils.d.ts +6 -0
  269. package/server-dist/utils.d.ts.map +1 -0
  270. package/server-dist/utils.js +9 -0
  271. package/server-dist/utils.js.map +1 -0
@@ -0,0 +1,194 @@
1
+ import { getDb } from './client.js';
2
+ // ──────────────────────────────────────────────────────
3
+ // Helpers
4
+ // ──────────────────────────────────────────────────────
5
+ /**
6
+ * Safely parse the models_used JSON column.
7
+ * Returns undefined on any parse failure rather than throwing — a corrupt
8
+ * value in one row should not break the entire query.
9
+ */
10
+ function parseModelsUsed(raw) {
11
+ if (!raw)
12
+ return undefined;
13
+ try {
14
+ return JSON.parse(raw);
15
+ }
16
+ catch {
17
+ return undefined;
18
+ }
19
+ }
20
+ // ──────────────────────────────────────────────────────
21
+ // Session existence check (used by sync)
22
+ // ──────────────────────────────────────────────────────
23
+ export function sessionExists(sessionId) {
24
+ const db = getDb();
25
+ const row = db.prepare('SELECT 1 FROM sessions WHERE id = ?').get(sessionId);
26
+ return row !== undefined;
27
+ }
28
+ export function getProjects() {
29
+ const db = getDb();
30
+ return db.prepare(`
31
+ SELECT id, name, path, session_count, last_activity
32
+ FROM projects
33
+ ORDER BY last_activity DESC
34
+ `).all();
35
+ }
36
+ /**
37
+ * Query sessions from SQLite, mapping snake_case columns to SessionRow shape.
38
+ */
39
+ export function getSessions(opts = {}) {
40
+ const db = getDb();
41
+ const conditions = [];
42
+ const params = [];
43
+ if (opts.periodStart) {
44
+ conditions.push('started_at >= ?');
45
+ params.push(opts.periodStart.toISOString());
46
+ }
47
+ if (opts.projectId) {
48
+ conditions.push('project_id = ?');
49
+ params.push(opts.projectId);
50
+ }
51
+ if (opts.sourceTool) {
52
+ conditions.push('source_tool = ?');
53
+ params.push(opts.sourceTool);
54
+ }
55
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
56
+ const sql = `
57
+ SELECT
58
+ id, project_id, project_name,
59
+ started_at, ended_at,
60
+ message_count, user_message_count, assistant_message_count, tool_call_count,
61
+ estimated_cost_usd, total_input_tokens, total_output_tokens,
62
+ cache_creation_tokens, cache_read_tokens,
63
+ primary_model, models_used,
64
+ generated_title, custom_title, summary, session_character,
65
+ source_tool, usage_source
66
+ FROM sessions
67
+ ${where}
68
+ ORDER BY started_at DESC
69
+ `;
70
+ const rows = db.prepare(sql).all(...params);
71
+ return rows.map((r) => ({
72
+ id: r.id,
73
+ projectId: r.project_id,
74
+ projectName: r.project_name,
75
+ startedAt: new Date(r.started_at),
76
+ endedAt: new Date(r.ended_at),
77
+ messageCount: r.message_count,
78
+ userMessageCount: r.user_message_count,
79
+ assistantMessageCount: r.assistant_message_count,
80
+ toolCallCount: r.tool_call_count,
81
+ estimatedCostUsd: r.estimated_cost_usd ?? undefined,
82
+ totalInputTokens: r.total_input_tokens ?? undefined,
83
+ totalOutputTokens: r.total_output_tokens ?? undefined,
84
+ cacheCreationTokens: r.cache_creation_tokens ?? undefined,
85
+ cacheReadTokens: r.cache_read_tokens ?? undefined,
86
+ primaryModel: r.primary_model ?? undefined,
87
+ modelsUsed: parseModelsUsed(r.models_used),
88
+ generatedTitle: r.generated_title ?? undefined,
89
+ customTitle: r.custom_title ?? undefined,
90
+ summary: r.summary ?? undefined,
91
+ sessionCharacter: r.session_character ?? undefined,
92
+ sourceTool: r.source_tool,
93
+ usageSource: r.usage_source ?? undefined,
94
+ }));
95
+ }
96
+ /**
97
+ * Get the most recent session matching optional filters.
98
+ * Uses LIMIT 1 to avoid scanning all rows.
99
+ */
100
+ export function getLastSession(opts) {
101
+ const db = getDb();
102
+ const conditions = [];
103
+ const params = [];
104
+ if (opts?.sourceTool) {
105
+ conditions.push('source_tool = ?');
106
+ params.push(opts.sourceTool);
107
+ }
108
+ if (opts?.projectId) {
109
+ conditions.push('project_id = ?');
110
+ params.push(opts.projectId);
111
+ }
112
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
113
+ const sql = `
114
+ SELECT
115
+ id, project_id, project_name,
116
+ started_at, ended_at,
117
+ message_count, user_message_count, assistant_message_count, tool_call_count,
118
+ estimated_cost_usd, total_input_tokens, total_output_tokens,
119
+ cache_creation_tokens, cache_read_tokens,
120
+ primary_model, models_used,
121
+ generated_title, custom_title, summary, session_character,
122
+ source_tool, usage_source
123
+ FROM sessions
124
+ ${where}
125
+ ORDER BY started_at DESC
126
+ LIMIT 1
127
+ `;
128
+ const row = db.prepare(sql).get(...params);
129
+ if (!row)
130
+ return null;
131
+ return {
132
+ id: row.id,
133
+ projectId: row.project_id,
134
+ projectName: row.project_name,
135
+ startedAt: new Date(row.started_at),
136
+ endedAt: new Date(row.ended_at),
137
+ messageCount: row.message_count,
138
+ userMessageCount: row.user_message_count,
139
+ assistantMessageCount: row.assistant_message_count,
140
+ toolCallCount: row.tool_call_count,
141
+ estimatedCostUsd: row.estimated_cost_usd ?? undefined,
142
+ totalInputTokens: row.total_input_tokens ?? undefined,
143
+ totalOutputTokens: row.total_output_tokens ?? undefined,
144
+ cacheCreationTokens: row.cache_creation_tokens ?? undefined,
145
+ cacheReadTokens: row.cache_read_tokens ?? undefined,
146
+ primaryModel: row.primary_model ?? undefined,
147
+ modelsUsed: parseModelsUsed(row.models_used),
148
+ generatedTitle: row.generated_title ?? undefined,
149
+ customTitle: row.custom_title ?? undefined,
150
+ summary: row.summary ?? undefined,
151
+ sessionCharacter: row.session_character ?? undefined,
152
+ sourceTool: row.source_tool,
153
+ usageSource: row.usage_source ?? undefined,
154
+ };
155
+ }
156
+ /**
157
+ * Count sessions matching optional filters without loading row data.
158
+ * Use this instead of getSessions({}).length to avoid full-table scans
159
+ * when only a count is needed.
160
+ */
161
+ export function getSessionCount(opts = {}) {
162
+ const db = getDb();
163
+ const conditions = [];
164
+ const params = [];
165
+ if (opts.periodStart) {
166
+ conditions.push('started_at >= ?');
167
+ params.push(opts.periodStart.toISOString());
168
+ }
169
+ if (opts.projectId) {
170
+ conditions.push('project_id = ?');
171
+ params.push(opts.projectId);
172
+ }
173
+ if (opts.sourceTool) {
174
+ conditions.push('source_tool = ?');
175
+ params.push(opts.sourceTool);
176
+ }
177
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
178
+ const sql = `SELECT COUNT(*) AS cnt FROM sessions ${where}`;
179
+ const row = db.prepare(sql).get(...params);
180
+ return row.cnt;
181
+ }
182
+ /**
183
+ * Get distinct project names and IDs from sessions table.
184
+ * Used for project resolution in stats commands.
185
+ */
186
+ export function getProjectList() {
187
+ const db = getDb();
188
+ return db.prepare(`
189
+ SELECT DISTINCT project_id AS id, project_name AS name
190
+ FROM sessions
191
+ ORDER BY project_name
192
+ `).all();
193
+ }
194
+ //# sourceMappingURL=read.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read.js","sourceRoot":"","sources":["../../src/db/read.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export declare const SCHEMA_SQL = "\n-- ============================================================\n-- Projects\n-- ============================================================\nCREATE TABLE IF NOT EXISTS projects (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n path TEXT NOT NULL,\n git_remote_url TEXT,\n project_id_source TEXT NOT NULL DEFAULT 'path-hash',\n session_count INTEGER NOT NULL DEFAULT 0,\n last_activity TEXT NOT NULL,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now')),\n total_input_tokens INTEGER DEFAULT 0,\n total_output_tokens INTEGER DEFAULT 0,\n cache_creation_tokens INTEGER DEFAULT 0,\n cache_read_tokens INTEGER DEFAULT 0,\n estimated_cost_usd REAL DEFAULT 0\n);\n\n-- ============================================================\n-- Sessions\n-- ============================================================\nCREATE TABLE IF NOT EXISTS sessions (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id),\n project_name TEXT NOT NULL,\n project_path TEXT NOT NULL,\n git_remote_url TEXT,\n summary TEXT,\n custom_title TEXT,\n generated_title TEXT,\n title_source TEXT,\n session_character TEXT,\n started_at TEXT NOT NULL,\n ended_at TEXT NOT NULL,\n message_count INTEGER NOT NULL DEFAULT 0,\n user_message_count INTEGER NOT NULL DEFAULT 0,\n assistant_message_count INTEGER NOT NULL DEFAULT 0,\n tool_call_count INTEGER NOT NULL DEFAULT 0,\n git_branch TEXT,\n claude_version TEXT,\n source_tool TEXT NOT NULL DEFAULT 'claude-code',\n device_id TEXT,\n device_hostname TEXT,\n device_platform TEXT,\n synced_at TEXT NOT NULL DEFAULT (datetime('now')),\n total_input_tokens INTEGER,\n total_output_tokens INTEGER,\n cache_creation_tokens INTEGER,\n cache_read_tokens INTEGER,\n estimated_cost_usd REAL,\n models_used TEXT,\n primary_model TEXT,\n usage_source TEXT\n);\n\nCREATE INDEX IF NOT EXISTS idx_sessions_project_id ON sessions(project_id);\nCREATE INDEX IF NOT EXISTS idx_sessions_started_at ON sessions(started_at DESC);\nCREATE INDEX IF NOT EXISTS idx_sessions_source_tool ON sessions(source_tool);\n\n-- ============================================================\n-- Messages\n-- ============================================================\nCREATE TABLE IF NOT EXISTS messages (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n type TEXT NOT NULL,\n content TEXT NOT NULL DEFAULT '',\n thinking TEXT,\n tool_calls TEXT,\n tool_results TEXT,\n usage TEXT,\n timestamp TEXT NOT NULL,\n parent_id TEXT\n);\n\nCREATE INDEX IF NOT EXISTS idx_messages_session_id ON messages(session_id);\nCREATE INDEX IF NOT EXISTS idx_messages_timestamp ON messages(session_id, timestamp ASC);\n\n-- ============================================================\n-- Insights (written by dashboard server, not CLI)\n-- ============================================================\nCREATE TABLE IF NOT EXISTS insights (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n project_id TEXT NOT NULL REFERENCES projects(id),\n project_name TEXT NOT NULL,\n type TEXT NOT NULL,\n title TEXT NOT NULL,\n content TEXT NOT NULL,\n summary TEXT NOT NULL,\n bullets TEXT,\n confidence REAL NOT NULL,\n source TEXT NOT NULL DEFAULT 'llm',\n metadata TEXT,\n timestamp TEXT NOT NULL,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n scope TEXT NOT NULL DEFAULT 'session',\n analysis_version TEXT NOT NULL DEFAULT '1.0.0',\n linked_insight_ids TEXT\n);\n\nCREATE INDEX IF NOT EXISTS idx_insights_session_id ON insights(session_id);\nCREATE INDEX IF NOT EXISTS idx_insights_project_id ON insights(project_id);\nCREATE INDEX IF NOT EXISTS idx_insights_type ON insights(type);\nCREATE INDEX IF NOT EXISTS idx_insights_timestamp ON insights(timestamp DESC);\n\n-- ============================================================\n-- Global usage stats (singleton row, updated by CLI sync)\n-- ============================================================\nCREATE TABLE IF NOT EXISTS usage_stats (\n id INTEGER PRIMARY KEY CHECK (id = 1),\n total_input_tokens INTEGER DEFAULT 0,\n total_output_tokens INTEGER DEFAULT 0,\n cache_creation_tokens INTEGER DEFAULT 0,\n cache_read_tokens INTEGER DEFAULT 0,\n estimated_cost_usd REAL DEFAULT 0,\n sessions_with_usage INTEGER DEFAULT 0,\n last_updated_at TEXT DEFAULT (datetime('now'))\n);\n";
2
+ export declare const CURRENT_SCHEMA_VERSION = 1;
3
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/db/schema.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,UAAU,m9JA0HtB,CAAC;AAEF,eAAO,MAAM,sBAAsB,IAAI,CAAC"}
@@ -0,0 +1,129 @@
1
+ // SQL schema for local SQLite database at ~/.code-insights/data.db
2
+ // Mirrors Firestore collections but normalized into relational tables.
3
+ // All timestamps are ISO 8601 strings (SQLite has no native datetime).
4
+ // Arrays and nested objects are stored as JSON strings.
5
+ export const SCHEMA_SQL = `
6
+ -- ============================================================
7
+ -- Projects
8
+ -- ============================================================
9
+ CREATE TABLE IF NOT EXISTS projects (
10
+ id TEXT PRIMARY KEY,
11
+ name TEXT NOT NULL,
12
+ path TEXT NOT NULL,
13
+ git_remote_url TEXT,
14
+ project_id_source TEXT NOT NULL DEFAULT 'path-hash',
15
+ session_count INTEGER NOT NULL DEFAULT 0,
16
+ last_activity TEXT NOT NULL,
17
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
18
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
19
+ total_input_tokens INTEGER DEFAULT 0,
20
+ total_output_tokens INTEGER DEFAULT 0,
21
+ cache_creation_tokens INTEGER DEFAULT 0,
22
+ cache_read_tokens INTEGER DEFAULT 0,
23
+ estimated_cost_usd REAL DEFAULT 0
24
+ );
25
+
26
+ -- ============================================================
27
+ -- Sessions
28
+ -- ============================================================
29
+ CREATE TABLE IF NOT EXISTS sessions (
30
+ id TEXT PRIMARY KEY,
31
+ project_id TEXT NOT NULL REFERENCES projects(id),
32
+ project_name TEXT NOT NULL,
33
+ project_path TEXT NOT NULL,
34
+ git_remote_url TEXT,
35
+ summary TEXT,
36
+ custom_title TEXT,
37
+ generated_title TEXT,
38
+ title_source TEXT,
39
+ session_character TEXT,
40
+ started_at TEXT NOT NULL,
41
+ ended_at TEXT NOT NULL,
42
+ message_count INTEGER NOT NULL DEFAULT 0,
43
+ user_message_count INTEGER NOT NULL DEFAULT 0,
44
+ assistant_message_count INTEGER NOT NULL DEFAULT 0,
45
+ tool_call_count INTEGER NOT NULL DEFAULT 0,
46
+ git_branch TEXT,
47
+ claude_version TEXT,
48
+ source_tool TEXT NOT NULL DEFAULT 'claude-code',
49
+ device_id TEXT,
50
+ device_hostname TEXT,
51
+ device_platform TEXT,
52
+ synced_at TEXT NOT NULL DEFAULT (datetime('now')),
53
+ total_input_tokens INTEGER,
54
+ total_output_tokens INTEGER,
55
+ cache_creation_tokens INTEGER,
56
+ cache_read_tokens INTEGER,
57
+ estimated_cost_usd REAL,
58
+ models_used TEXT,
59
+ primary_model TEXT,
60
+ usage_source TEXT
61
+ );
62
+
63
+ CREATE INDEX IF NOT EXISTS idx_sessions_project_id ON sessions(project_id);
64
+ CREATE INDEX IF NOT EXISTS idx_sessions_started_at ON sessions(started_at DESC);
65
+ CREATE INDEX IF NOT EXISTS idx_sessions_source_tool ON sessions(source_tool);
66
+
67
+ -- ============================================================
68
+ -- Messages
69
+ -- ============================================================
70
+ CREATE TABLE IF NOT EXISTS messages (
71
+ id TEXT PRIMARY KEY,
72
+ session_id TEXT NOT NULL REFERENCES sessions(id),
73
+ type TEXT NOT NULL,
74
+ content TEXT NOT NULL DEFAULT '',
75
+ thinking TEXT,
76
+ tool_calls TEXT,
77
+ tool_results TEXT,
78
+ usage TEXT,
79
+ timestamp TEXT NOT NULL,
80
+ parent_id TEXT
81
+ );
82
+
83
+ CREATE INDEX IF NOT EXISTS idx_messages_session_id ON messages(session_id);
84
+ CREATE INDEX IF NOT EXISTS idx_messages_timestamp ON messages(session_id, timestamp ASC);
85
+
86
+ -- ============================================================
87
+ -- Insights (written by dashboard server, not CLI)
88
+ -- ============================================================
89
+ CREATE TABLE IF NOT EXISTS insights (
90
+ id TEXT PRIMARY KEY,
91
+ session_id TEXT NOT NULL REFERENCES sessions(id),
92
+ project_id TEXT NOT NULL REFERENCES projects(id),
93
+ project_name TEXT NOT NULL,
94
+ type TEXT NOT NULL,
95
+ title TEXT NOT NULL,
96
+ content TEXT NOT NULL,
97
+ summary TEXT NOT NULL,
98
+ bullets TEXT,
99
+ confidence REAL NOT NULL,
100
+ source TEXT NOT NULL DEFAULT 'llm',
101
+ metadata TEXT,
102
+ timestamp TEXT NOT NULL,
103
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
104
+ scope TEXT NOT NULL DEFAULT 'session',
105
+ analysis_version TEXT NOT NULL DEFAULT '1.0.0',
106
+ linked_insight_ids TEXT
107
+ );
108
+
109
+ CREATE INDEX IF NOT EXISTS idx_insights_session_id ON insights(session_id);
110
+ CREATE INDEX IF NOT EXISTS idx_insights_project_id ON insights(project_id);
111
+ CREATE INDEX IF NOT EXISTS idx_insights_type ON insights(type);
112
+ CREATE INDEX IF NOT EXISTS idx_insights_timestamp ON insights(timestamp DESC);
113
+
114
+ -- ============================================================
115
+ -- Global usage stats (singleton row, updated by CLI sync)
116
+ -- ============================================================
117
+ CREATE TABLE IF NOT EXISTS usage_stats (
118
+ id INTEGER PRIMARY KEY CHECK (id = 1),
119
+ total_input_tokens INTEGER DEFAULT 0,
120
+ total_output_tokens INTEGER DEFAULT 0,
121
+ cache_creation_tokens INTEGER DEFAULT 0,
122
+ cache_read_tokens INTEGER DEFAULT 0,
123
+ estimated_cost_usd REAL DEFAULT 0,
124
+ sessions_with_usage INTEGER DEFAULT 0,
125
+ last_updated_at TEXT DEFAULT (datetime('now'))
126
+ );
127
+ `;
128
+ export const CURRENT_SCHEMA_VERSION = 1;
129
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/db/schema.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,uEAAuE;AACvE,uEAAuE;AACvE,wDAAwD;AAExD,MAAM,CAAC,MAAM,UAAU,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0HzB,CAAC;AAEF,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC"}
@@ -0,0 +1,24 @@
1
+ import type { ParsedSession } from '../types.js';
2
+ /**
3
+ * Upsert project record and insert session + messages.
4
+ * Replaces firebase/client.ts uploadSession() + uploadMessages().
5
+ *
6
+ * isForce: when true (--force sync), usage stats on the project are NOT
7
+ * incrementally added — they'll be recalculated via recalculateUsageStats().
8
+ */
9
+ export declare function insertSessionWithProject(session: ParsedSession, isForce?: boolean): void;
10
+ /**
11
+ * Insert messages for a session.
12
+ * Replaces firebase/client.ts uploadMessages().
13
+ */
14
+ export declare function insertMessages(session: ParsedSession): void;
15
+ /**
16
+ * After a --force sync, recalculate usage_stats singleton from all sessions.
17
+ * Also recalculates per-project usage totals.
18
+ */
19
+ export declare function recalculateUsageStats(): {
20
+ sessionsWithUsage: number;
21
+ totalTokens: number;
22
+ estimatedCostUsd: number;
23
+ };
24
+ //# sourceMappingURL=write.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write.d.ts","sourceRoot":"","sources":["../../src/db/write.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAiB,MAAM,aAAa,CAAC;AA4JhE;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,aAAa,EAAE,OAAO,UAAQ,GAAG,IAAI,CAgBtF;AA8ED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,CAmC3D;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,IAAI;IAAE,iBAAiB,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAA;CAAE,CAmHpH"}
@@ -0,0 +1,287 @@
1
+ import { getDb } from './client.js';
2
+ import { sessionExists } from './read.js';
3
+ import { generateStableProjectId, getDeviceInfo } from '../utils/device.js';
4
+ const CONTENT_MAX = 10000;
5
+ const THINKING_MAX = 5000;
6
+ const TOOL_RESULT_MAX = 2000;
7
+ const TOOL_INPUT_MAX = 1000;
8
+ function truncate(s, max) {
9
+ if (s.length <= max)
10
+ return s;
11
+ return s.slice(0, max - 20) + '\n... [truncated]';
12
+ }
13
+ // ──────────────────────────────────────────────────────
14
+ // Module-level lazy-initialized prepared statements.
15
+ //
16
+ // We can't prepare at module load time because the DB isn't open yet.
17
+ // Instead, we lazy-init on first use — a statement is compiled once and
18
+ // reused on every subsequent call, avoiding repeated parse overhead during
19
+ // batch syncs that write hundreds of sessions.
20
+ // ──────────────────────────────────────────────────────
21
+ let _lastDb = null;
22
+ // All cached statements below — reset when DB instance changes (e.g., test teardown)
23
+ let _stmtUpsertProjectBase = null;
24
+ let _stmtUpdateProjectWithUsage = null;
25
+ let _stmtUpdateProjectCountOnly = null;
26
+ let _stmtUpsertSession = null;
27
+ let _stmtInsertMessage = null;
28
+ let _stmtUpsertUsageStatsIncrement = null;
29
+ function getStmts() {
30
+ const db = getDb();
31
+ // If the DB instance changed (test isolation, reset), rebuild all statements
32
+ if (db !== _lastDb) {
33
+ _lastDb = db;
34
+ _stmtUpsertProjectBase = null;
35
+ _stmtUpdateProjectWithUsage = null;
36
+ _stmtUpdateProjectCountOnly = null;
37
+ _stmtUpsertSession = null;
38
+ _stmtInsertMessage = null;
39
+ _stmtUpsertUsageStatsIncrement = null;
40
+ }
41
+ if (!_stmtUpsertProjectBase) {
42
+ _stmtUpsertProjectBase = db.prepare(`
43
+ INSERT INTO projects (id, name, path, git_remote_url, project_id_source, last_activity)
44
+ VALUES (?, ?, ?, ?, ?, ?)
45
+ ON CONFLICT(id) DO UPDATE SET
46
+ name = excluded.name,
47
+ path = excluded.path,
48
+ git_remote_url = excluded.git_remote_url,
49
+ last_activity = MAX(last_activity, excluded.last_activity),
50
+ updated_at = datetime('now')
51
+ `);
52
+ }
53
+ if (!_stmtUpdateProjectWithUsage) {
54
+ _stmtUpdateProjectWithUsage = db.prepare(`
55
+ UPDATE projects SET
56
+ session_count = session_count + 1,
57
+ total_input_tokens = total_input_tokens + ?,
58
+ total_output_tokens = total_output_tokens + ?,
59
+ cache_creation_tokens = cache_creation_tokens + ?,
60
+ cache_read_tokens = cache_read_tokens + ?,
61
+ estimated_cost_usd = estimated_cost_usd + ?,
62
+ updated_at = datetime('now')
63
+ WHERE id = ?
64
+ `);
65
+ }
66
+ if (!_stmtUpdateProjectCountOnly) {
67
+ _stmtUpdateProjectCountOnly = db.prepare(`
68
+ UPDATE projects SET session_count = session_count + 1, updated_at = datetime('now')
69
+ WHERE id = ?
70
+ `);
71
+ }
72
+ if (!_stmtUpsertSession) {
73
+ _stmtUpsertSession = db.prepare(`
74
+ INSERT INTO sessions (
75
+ id, project_id, project_name, project_path, git_remote_url,
76
+ summary, generated_title, title_source, session_character,
77
+ started_at, ended_at,
78
+ message_count, user_message_count, assistant_message_count, tool_call_count,
79
+ git_branch, claude_version, source_tool,
80
+ device_id, device_hostname, device_platform,
81
+ total_input_tokens, total_output_tokens, cache_creation_tokens, cache_read_tokens,
82
+ estimated_cost_usd, models_used, primary_model, usage_source
83
+ ) VALUES (
84
+ ?, ?, ?, ?, ?,
85
+ ?, ?, ?, ?,
86
+ ?, ?,
87
+ ?, ?, ?, ?,
88
+ ?, ?, ?,
89
+ ?, ?, ?,
90
+ ?, ?, ?, ?,
91
+ ?, ?, ?, ?
92
+ )
93
+ ON CONFLICT(id) DO UPDATE SET
94
+ generated_title = excluded.generated_title,
95
+ title_source = excluded.title_source,
96
+ session_character = excluded.session_character,
97
+ ended_at = excluded.ended_at,
98
+ summary = excluded.summary,
99
+ message_count = excluded.message_count,
100
+ user_message_count = excluded.user_message_count,
101
+ assistant_message_count = excluded.assistant_message_count,
102
+ tool_call_count = excluded.tool_call_count,
103
+ total_input_tokens = excluded.total_input_tokens,
104
+ total_output_tokens = excluded.total_output_tokens,
105
+ cache_creation_tokens = excluded.cache_creation_tokens,
106
+ cache_read_tokens = excluded.cache_read_tokens,
107
+ estimated_cost_usd = excluded.estimated_cost_usd,
108
+ models_used = excluded.models_used,
109
+ primary_model = excluded.primary_model,
110
+ usage_source = excluded.usage_source,
111
+ synced_at = datetime('now')
112
+ `);
113
+ }
114
+ if (!_stmtInsertMessage) {
115
+ _stmtInsertMessage = db.prepare(`
116
+ INSERT OR IGNORE INTO messages (
117
+ id, session_id, type, content, thinking,
118
+ tool_calls, tool_results, usage, timestamp, parent_id
119
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
120
+ `);
121
+ }
122
+ if (!_stmtUpsertUsageStatsIncrement) {
123
+ _stmtUpsertUsageStatsIncrement = db.prepare(`
124
+ INSERT INTO usage_stats (id, total_input_tokens, total_output_tokens, cache_creation_tokens, cache_read_tokens, estimated_cost_usd, sessions_with_usage, last_updated_at)
125
+ VALUES (1, ?, ?, ?, ?, ?, 1, datetime('now'))
126
+ ON CONFLICT(id) DO UPDATE SET
127
+ total_input_tokens = total_input_tokens + excluded.total_input_tokens,
128
+ total_output_tokens = total_output_tokens + excluded.total_output_tokens,
129
+ cache_creation_tokens = cache_creation_tokens + excluded.cache_creation_tokens,
130
+ cache_read_tokens = cache_read_tokens + excluded.cache_read_tokens,
131
+ estimated_cost_usd = estimated_cost_usd + excluded.estimated_cost_usd,
132
+ sessions_with_usage = sessions_with_usage + 1,
133
+ last_updated_at = datetime('now')
134
+ `);
135
+ }
136
+ return {
137
+ upsertProjectBase: _stmtUpsertProjectBase,
138
+ updateProjectWithUsage: _stmtUpdateProjectWithUsage,
139
+ updateProjectCountOnly: _stmtUpdateProjectCountOnly,
140
+ upsertSession: _stmtUpsertSession,
141
+ insertMessage: _stmtInsertMessage,
142
+ upsertUsageStatsIncrement: _stmtUpsertUsageStatsIncrement,
143
+ };
144
+ }
145
+ /**
146
+ * Upsert project record and insert session + messages.
147
+ * Replaces firebase/client.ts uploadSession() + uploadMessages().
148
+ *
149
+ * isForce: when true (--force sync), usage stats on the project are NOT
150
+ * incrementally added — they'll be recalculated via recalculateUsageStats().
151
+ */
152
+ export function insertSessionWithProject(session, isForce = false) {
153
+ const db = getDb();
154
+ const { projectId, source: projectIdSource, gitRemoteUrl } = generateStableProjectId(session.projectPath);
155
+ const deviceInfo = getDeviceInfo();
156
+ const isNew = !sessionExists(session.id);
157
+ const tx = db.transaction(() => {
158
+ upsertProject(projectId, session, projectIdSource, gitRemoteUrl, isNew, isForce);
159
+ upsertSession(session, projectId, gitRemoteUrl, deviceInfo);
160
+ if (isNew) {
161
+ updateGlobalUsageStats(session, isForce);
162
+ }
163
+ });
164
+ tx();
165
+ }
166
+ function upsertProject(projectId, session, projectIdSource, gitRemoteUrl, isNewSession, isForce) {
167
+ const stmts = getStmts();
168
+ // Insert project if it doesn't exist
169
+ stmts.upsertProjectBase.run(projectId, session.projectName, session.projectPath, gitRemoteUrl, projectIdSource, session.endedAt.toISOString());
170
+ // Increment session count and usage only for genuinely new sessions
171
+ if (isNewSession && !isForce && session.usage) {
172
+ stmts.updateProjectWithUsage.run(session.usage.totalInputTokens, session.usage.totalOutputTokens, session.usage.cacheCreationTokens, session.usage.cacheReadTokens, session.usage.estimatedCostUsd, projectId);
173
+ }
174
+ else if (isNewSession) {
175
+ stmts.updateProjectCountOnly.run(projectId);
176
+ }
177
+ }
178
+ function upsertSession(session, projectId, gitRemoteUrl, deviceInfo) {
179
+ const stmts = getStmts();
180
+ stmts.upsertSession.run(session.id, projectId, session.projectName, session.projectPath, gitRemoteUrl, session.summary, session.generatedTitle, session.titleSource, session.sessionCharacter, session.startedAt.toISOString(), session.endedAt.toISOString(), session.messageCount, session.userMessageCount, session.assistantMessageCount, session.toolCallCount, session.gitBranch, session.claudeVersion, session.sourceTool ?? 'claude-code', deviceInfo.deviceId, deviceInfo.hostname, deviceInfo.platform, session.usage?.totalInputTokens ?? null, session.usage?.totalOutputTokens ?? null, session.usage?.cacheCreationTokens ?? null, session.usage?.cacheReadTokens ?? null, session.usage?.estimatedCostUsd ?? null, session.usage?.modelsUsed ? JSON.stringify(session.usage.modelsUsed) : null, session.usage?.primaryModel ?? null, session.usage?.usageSource ?? null);
181
+ }
182
+ /**
183
+ * Insert messages for a session.
184
+ * Replaces firebase/client.ts uploadMessages().
185
+ */
186
+ export function insertMessages(session) {
187
+ if (session.messages.length === 0)
188
+ return;
189
+ const db = getDb();
190
+ const stmts = getStmts();
191
+ const tx = db.transaction((messages) => {
192
+ for (const msg of messages) {
193
+ stmts.insertMessage.run(msg.id, msg.sessionId, msg.type, truncate(msg.content, CONTENT_MAX), msg.thinking ? truncate(msg.thinking, THINKING_MAX) : null, msg.toolCalls.length > 0
194
+ ? JSON.stringify(msg.toolCalls.map(tc => ({
195
+ id: tc.id,
196
+ name: tc.name,
197
+ input: JSON.stringify(tc.input).slice(0, TOOL_INPUT_MAX),
198
+ })))
199
+ : null, msg.toolResults.length > 0
200
+ ? JSON.stringify(msg.toolResults.map(tr => ({
201
+ toolUseId: tr.toolUseId,
202
+ output: truncate(tr.output, TOOL_RESULT_MAX),
203
+ })))
204
+ : null, msg.usage ? JSON.stringify(msg.usage) : null, msg.timestamp.toISOString(), msg.parentId);
205
+ }
206
+ });
207
+ tx(session.messages);
208
+ }
209
+ /**
210
+ * After a --force sync, recalculate usage_stats singleton from all sessions.
211
+ * Also recalculates per-project usage totals.
212
+ */
213
+ export function recalculateUsageStats() {
214
+ const db = getDb();
215
+ const tx = db.transaction(() => {
216
+ // Aggregate global stats from all sessions that have usage data
217
+ const global = db.prepare(`
218
+ SELECT
219
+ COUNT(*) AS sessions_with_usage,
220
+ SUM(total_input_tokens) AS total_input,
221
+ SUM(total_output_tokens) AS total_output,
222
+ SUM(cache_creation_tokens) AS cache_creation,
223
+ SUM(cache_read_tokens) AS cache_read,
224
+ SUM(estimated_cost_usd) AS total_cost
225
+ FROM sessions
226
+ WHERE usage_source IS NOT NULL
227
+ `).get();
228
+ // Upsert the singleton usage_stats row
229
+ db.prepare(`
230
+ INSERT INTO usage_stats (id, total_input_tokens, total_output_tokens, cache_creation_tokens, cache_read_tokens, estimated_cost_usd, sessions_with_usage, last_updated_at)
231
+ VALUES (1, ?, ?, ?, ?, ?, ?, datetime('now'))
232
+ ON CONFLICT(id) DO UPDATE SET
233
+ total_input_tokens = excluded.total_input_tokens,
234
+ total_output_tokens = excluded.total_output_tokens,
235
+ cache_creation_tokens = excluded.cache_creation_tokens,
236
+ cache_read_tokens = excluded.cache_read_tokens,
237
+ estimated_cost_usd = excluded.estimated_cost_usd,
238
+ sessions_with_usage = excluded.sessions_with_usage,
239
+ last_updated_at = excluded.last_updated_at
240
+ `).run(global.total_input ?? 0, global.total_output ?? 0, global.cache_creation ?? 0, global.cache_read ?? 0, global.total_cost ?? 0, global.sessions_with_usage ?? 0);
241
+ // Recalculate per-project usage totals
242
+ const perProject = db.prepare(`
243
+ SELECT
244
+ project_id,
245
+ COUNT(*) AS session_count,
246
+ SUM(total_input_tokens) AS total_input,
247
+ SUM(total_output_tokens) AS total_output,
248
+ SUM(cache_creation_tokens) AS cache_creation,
249
+ SUM(cache_read_tokens) AS cache_read,
250
+ SUM(estimated_cost_usd) AS total_cost,
251
+ MAX(ended_at) AS last_activity
252
+ FROM sessions
253
+ GROUP BY project_id
254
+ `).all();
255
+ const updateProject = db.prepare(`
256
+ UPDATE projects SET
257
+ session_count = ?,
258
+ total_input_tokens = ?,
259
+ total_output_tokens = ?,
260
+ cache_creation_tokens = ?,
261
+ cache_read_tokens = ?,
262
+ estimated_cost_usd = ?,
263
+ last_activity = ?,
264
+ updated_at = datetime('now')
265
+ WHERE id = ?
266
+ `);
267
+ for (const row of perProject) {
268
+ updateProject.run(row.session_count, row.total_input ?? 0, row.total_output ?? 0, row.cache_creation ?? 0, row.cache_read ?? 0, row.total_cost ?? 0, row.last_activity, row.project_id);
269
+ }
270
+ return global;
271
+ });
272
+ const result = tx();
273
+ const totalTokens = (result.total_input ?? 0) + (result.total_output ?? 0)
274
+ + (result.cache_creation ?? 0) + (result.cache_read ?? 0);
275
+ return {
276
+ sessionsWithUsage: result.sessions_with_usage ?? 0,
277
+ totalTokens,
278
+ estimatedCostUsd: result.total_cost ?? 0,
279
+ };
280
+ }
281
+ function updateGlobalUsageStats(session, isForce) {
282
+ if (isForce || !session.usage)
283
+ return;
284
+ const stmts = getStmts();
285
+ stmts.upsertUsageStatsIncrement.run(session.usage.totalInputTokens, session.usage.totalOutputTokens, session.usage.cacheCreationTokens, session.usage.cacheReadTokens, session.usage.estimatedCostUsd);
286
+ }
287
+ //# sourceMappingURL=write.js.map