codesesh 0.3.0 → 0.4.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.
package/README.md CHANGED
@@ -20,9 +20,12 @@ Your browser will open at `http://localhost:4321` with all your sessions ready t
20
20
 
21
21
  - **Unified Timeline** — Browse sessions across all your AI agents in a single, searchable interface
22
22
  - **Full-Text Search** — Search across session titles and conversation content with highlighted matches
23
- - **Dashboard & Activity Trends** — See totals, daily activity, agent distribution, and recent sessions
23
+ - **Dashboard & Activity Trends** — See totals, daily activity, agent distribution, model usage, token trends, bookmarks, and recent sessions
24
+ - **Bookmarks** — Save important sessions and keep them visible from the dashboard
24
25
  - **Full Conversation Replay** — Read every message, tool call, and reasoning step exactly as it happened
25
- - **Cost & Token Visibility** — See exactly how many tokens and dollars each session consumed
26
+ - **File Change Tracking** — Jump to files that were read, edited, created, deleted, or moved
27
+ - **Keyboard Navigation** — Move through views, focus search, and open shortcuts without leaving the keyboard
28
+ - **Cost & Token Visibility** — See token totals, cache tokens, model usage, and session cost
26
29
  - **SQLite-Backed Cache & Search Index** — Restore session lists quickly and reuse the same local store for search
27
30
  - **Zero Configuration** — Just run it. CodeSesh auto-discovers everything on your filesystem
28
31
  - **100% Local & Private** — Nothing leaves your machine. No accounts, no cloud sync, no telemetry
@@ -24,6 +24,8 @@ import { resolve, sep } from "path";
24
24
  import { existsSync as existsSync7, rmSync, unlinkSync } from "fs";
25
25
  import { join as join7 } from "path";
26
26
  import { homedir as homedir2 } from "os";
27
+ import { homedir as homedir3, platform as platform2 } from "os";
28
+ import { join as join8 } from "path";
27
29
  var registrations = [];
28
30
  function registerAgent(reg) {
29
31
  registrations.push(reg);
@@ -308,6 +310,8 @@ var ClaudeCodeAgent = class extends BaseAgent {
308
310
  let totalCost = 0;
309
311
  let totalInputTokens = 0;
310
312
  let totalOutputTokens = 0;
313
+ let totalCacheRead = 0;
314
+ let totalCacheCreate = 0;
311
315
  for (const record of parseJsonlLines(content)) {
312
316
  try {
313
317
  this.convertRecord(
@@ -325,6 +329,8 @@ var ClaudeCodeAgent = class extends BaseAgent {
325
329
  totalCost += msg.cost ?? 0;
326
330
  totalInputTokens += msg.tokens?.input ?? 0;
327
331
  totalOutputTokens += msg.tokens?.output ?? 0;
332
+ totalCacheRead += msg.tokens?.cache_read ?? 0;
333
+ totalCacheCreate += msg.tokens?.cache_create ?? 0;
328
334
  }
329
335
  return {
330
336
  id: meta.id,
@@ -338,7 +344,9 @@ var ClaudeCodeAgent = class extends BaseAgent {
338
344
  message_count: messages.length,
339
345
  total_input_tokens: totalInputTokens,
340
346
  total_output_tokens: totalOutputTokens,
341
- total_cost: totalCost
347
+ total_cost: totalCost,
348
+ total_cache_read_tokens: totalCacheRead,
349
+ total_cache_create_tokens: totalCacheCreate
342
350
  },
343
351
  messages
344
352
  };
@@ -512,6 +520,9 @@ var ClaudeCodeAgent = class extends BaseAgent {
512
520
  let cwd = null;
513
521
  let totalInputTokens = 0;
514
522
  let totalOutputTokens = 0;
523
+ let totalCacheReadTokens = 0;
524
+ let totalCacheCreateTokens = 0;
525
+ const modelUsageMap = {};
515
526
  for (const line of lines) {
516
527
  try {
517
528
  const data = JSON.parse(line);
@@ -533,8 +544,20 @@ var ClaudeCodeAgent = class extends BaseAgent {
533
544
  if (role === "assistant") {
534
545
  const usage = msg["usage"];
535
546
  if (usage && typeof usage === "object") {
536
- totalInputTokens += (usage["input_tokens"] ?? 0) + (usage["cache_creation_input_tokens"] ?? 0) + (usage["cache_read_input_tokens"] ?? 0);
537
- totalOutputTokens += usage["output_tokens"] ?? 0;
547
+ const inputTokens = usage["input_tokens"] ?? 0;
548
+ const cacheRead = usage["cache_read_input_tokens"] ?? 0;
549
+ const cacheCreate = usage["cache_creation_input_tokens"] ?? 0;
550
+ const outputTokens = usage["output_tokens"] ?? 0;
551
+ totalInputTokens += inputTokens + cacheRead + cacheCreate;
552
+ totalOutputTokens += outputTokens;
553
+ totalCacheReadTokens += cacheRead;
554
+ totalCacheCreateTokens += cacheCreate;
555
+ const m = msg["model"];
556
+ if (typeof m === "string" && m.trim()) {
557
+ const name = m.trim();
558
+ const msgTotal = inputTokens + cacheRead + cacheCreate + outputTokens;
559
+ modelUsageMap[name] = (modelUsageMap[name] ?? 0) + msgTotal;
560
+ }
538
561
  }
539
562
  }
540
563
  }
@@ -545,6 +568,7 @@ var ClaudeCodeAgent = class extends BaseAgent {
545
568
  const messageTitle = this.extractTitle(lines);
546
569
  const directoryTitle = basenameTitle(directory) || basenameTitle(projectDir);
547
570
  const title = resolveSessionTitle(explicitTitle, messageTitle, directoryTitle);
571
+ const hasModelUsage = Object.keys(modelUsageMap).length > 0;
548
572
  return {
549
573
  id: sessionId,
550
574
  slug: `claudecode/${sessionId}`,
@@ -556,8 +580,11 @@ var ClaudeCodeAgent = class extends BaseAgent {
556
580
  message_count: messageCount,
557
581
  total_input_tokens: totalInputTokens,
558
582
  total_output_tokens: totalOutputTokens,
559
- total_cost: 0
560
- }
583
+ total_cost: 0,
584
+ total_cache_read_tokens: totalCacheReadTokens,
585
+ total_cache_create_tokens: totalCacheCreateTokens
586
+ },
587
+ model_usage: hasModelUsage ? modelUsageMap : void 0
561
588
  };
562
589
  }
563
590
  extractTitle(lines) {
@@ -785,9 +812,13 @@ var ClaudeCodeAgent = class extends BaseAgent {
785
812
  const usage = msg["usage"];
786
813
  if (usage && typeof usage === "object" && !message.tokens) {
787
814
  const u = usage;
815
+ const cacheRead = u["cache_read_input_tokens"] ?? 0;
816
+ const cacheCreate = u["cache_creation_input_tokens"] ?? 0;
788
817
  message.tokens = {
789
- input: (u["input_tokens"] ?? 0) + (u["cache_creation_input_tokens"] ?? 0) + (u["cache_read_input_tokens"] ?? 0),
790
- output: u["output_tokens"] ?? 0
818
+ input: (u["input_tokens"] ?? 0) + cacheCreate + cacheRead,
819
+ output: u["output_tokens"] ?? 0,
820
+ cache_read: cacheRead,
821
+ cache_create: cacheCreate
791
822
  };
792
823
  }
793
824
  }
@@ -3087,6 +3118,16 @@ var CursorAgent = class extends BaseAgent {
3087
3118
  }
3088
3119
  const messageCount = messages.length;
3089
3120
  const directory = workspacePathMap.get(composerId) ?? "";
3121
+ const modelUsageMap = {};
3122
+ for (const msg of messages) {
3123
+ if (msg.model) {
3124
+ const msgTokens = (msg.tokens?.input ?? 0) + (msg.tokens?.output ?? 0);
3125
+ if (msgTokens > 0) {
3126
+ modelUsageMap[msg.model] = (modelUsageMap[msg.model] ?? 0) + msgTokens;
3127
+ }
3128
+ }
3129
+ }
3130
+ const hasModelUsage = Object.keys(modelUsageMap).length > 0;
3090
3131
  heads.push({
3091
3132
  id: sessionId,
3092
3133
  slug: `cursor/${sessionId}`,
@@ -3099,7 +3140,8 @@ var CursorAgent = class extends BaseAgent {
3099
3140
  total_input_tokens: composer.inputTokenCount ?? 0,
3100
3141
  total_output_tokens: composer.outputTokenCount ?? 0,
3101
3142
  total_cost: 0
3102
- }
3143
+ },
3144
+ model_usage: hasModelUsage ? modelUsageMap : void 0
3103
3145
  });
3104
3146
  this.composerCache.set(sessionId, composer);
3105
3147
  this.composerCache.set(`__mapping__${composerId}`, {
@@ -3507,7 +3549,7 @@ registerAgent({
3507
3549
  icon: "/icon/agent/cursor.svg",
3508
3550
  create: () => new CursorAgent()
3509
3551
  });
3510
- var CACHE_VERSION = 3;
3552
+ var CACHE_VERSION = 4;
3511
3553
  var CACHE_TTL = 7 * 24 * 60 * 60 * 1e3;
3512
3554
  var CACHE_FILENAME = "codesesh.db";
3513
3555
  var LEGACY_CACHE_FILENAME = "scan-cache.json";
@@ -4074,6 +4116,227 @@ async function scanSessions(options = {}, onProgress) {
4074
4116
  async function scanSessionsAsync(options = {}, onProgress) {
4075
4117
  return scanSessions(options, onProgress);
4076
4118
  }
4119
+ var BOOKMARK_DB_FILENAME = "state.db";
4120
+ var BOOKMARK_DB_VERSION = 1;
4121
+ var BookmarkStorageUnavailableError = class extends Error {
4122
+ constructor() {
4123
+ super("SQLite state database is unavailable");
4124
+ this.name = "BookmarkStorageUnavailableError";
4125
+ }
4126
+ };
4127
+ function getStateDir() {
4128
+ const p = platform2();
4129
+ if (p === "darwin") {
4130
+ return join8(homedir3(), "Library", "Application Support", "codesesh");
4131
+ }
4132
+ if (p === "win32") {
4133
+ const appData = process.env.APPDATA ?? process.env.LOCALAPPDATA;
4134
+ return join8(appData ?? join8(homedir3(), "AppData", "Roaming"), "codesesh");
4135
+ }
4136
+ return join8(process.env.XDG_DATA_HOME ?? join8(homedir3(), ".local", "share"), "codesesh");
4137
+ }
4138
+ function getStateDbPath() {
4139
+ return join8(getStateDir(), BOOKMARK_DB_FILENAME);
4140
+ }
4141
+ function ensureSchema2(db) {
4142
+ db.exec(`
4143
+ CREATE TABLE IF NOT EXISTS state_meta (
4144
+ key TEXT PRIMARY KEY,
4145
+ value TEXT NOT NULL
4146
+ );
4147
+
4148
+ CREATE TABLE IF NOT EXISTS bookmarks (
4149
+ agent_name TEXT NOT NULL,
4150
+ session_id TEXT NOT NULL,
4151
+ slug TEXT NOT NULL,
4152
+ title TEXT NOT NULL,
4153
+ directory TEXT NOT NULL,
4154
+ time_created INTEGER NOT NULL,
4155
+ time_updated INTEGER,
4156
+ stats_json TEXT NOT NULL,
4157
+ bookmarked_at INTEGER NOT NULL,
4158
+ PRIMARY KEY (agent_name, session_id)
4159
+ );
4160
+ `);
4161
+ const row = db.prepare("SELECT value FROM state_meta WHERE key = 'version'").get();
4162
+ const version = Number(row?.value ?? 0);
4163
+ if (version === BOOKMARK_DB_VERSION) return;
4164
+ db.prepare(
4165
+ `
4166
+ INSERT INTO state_meta(key, value)
4167
+ VALUES ('version', ?)
4168
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value
4169
+ `
4170
+ ).run(String(BOOKMARK_DB_VERSION));
4171
+ }
4172
+ function withStateDb(fn) {
4173
+ const db = openDb(getStateDbPath());
4174
+ if (!db) {
4175
+ throw new BookmarkStorageUnavailableError();
4176
+ }
4177
+ try {
4178
+ ensureSchema2(db);
4179
+ return fn(db);
4180
+ } finally {
4181
+ db.close();
4182
+ }
4183
+ }
4184
+ function toBookmarkRecord(row) {
4185
+ return {
4186
+ agentKey: String(row.agent_name ?? ""),
4187
+ sessionId: String(row.session_id ?? ""),
4188
+ fullPath: String(row.slug ?? ""),
4189
+ title: String(row.title ?? ""),
4190
+ directory: String(row.directory ?? ""),
4191
+ time_created: Number(row.time_created ?? 0),
4192
+ time_updated: row.time_updated == null ? void 0 : Number(row.time_updated),
4193
+ stats: JSON.parse(String(row.stats_json ?? "{}")),
4194
+ bookmarked_at: Number(row.bookmarked_at ?? 0)
4195
+ };
4196
+ }
4197
+ function listBookmarks() {
4198
+ return withStateDb((db) => {
4199
+ const rows = db.prepare(
4200
+ `
4201
+ SELECT
4202
+ agent_name,
4203
+ session_id,
4204
+ slug,
4205
+ title,
4206
+ directory,
4207
+ time_created,
4208
+ time_updated,
4209
+ stats_json,
4210
+ bookmarked_at
4211
+ FROM bookmarks
4212
+ ORDER BY COALESCE(time_updated, time_created) DESC, bookmarked_at DESC
4213
+ `
4214
+ ).all();
4215
+ return rows.map(toBookmarkRecord);
4216
+ });
4217
+ }
4218
+ function upsertBookmark(bookmark) {
4219
+ return withStateDb((db) => {
4220
+ const existing = db.prepare(
4221
+ `
4222
+ SELECT bookmarked_at
4223
+ FROM bookmarks
4224
+ WHERE agent_name = ? AND session_id = ?
4225
+ `
4226
+ ).get(bookmark.agentKey, bookmark.sessionId);
4227
+ const bookmarkedAt = Number(existing?.bookmarked_at ?? Date.now());
4228
+ db.prepare(
4229
+ `
4230
+ INSERT INTO bookmarks(
4231
+ agent_name,
4232
+ session_id,
4233
+ slug,
4234
+ title,
4235
+ directory,
4236
+ time_created,
4237
+ time_updated,
4238
+ stats_json,
4239
+ bookmarked_at
4240
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
4241
+ ON CONFLICT(agent_name, session_id) DO UPDATE SET
4242
+ slug = excluded.slug,
4243
+ title = excluded.title,
4244
+ directory = excluded.directory,
4245
+ time_created = excluded.time_created,
4246
+ time_updated = excluded.time_updated,
4247
+ stats_json = excluded.stats_json
4248
+ `
4249
+ ).run(
4250
+ bookmark.agentKey,
4251
+ bookmark.sessionId,
4252
+ bookmark.fullPath,
4253
+ bookmark.title,
4254
+ bookmark.directory,
4255
+ bookmark.time_created,
4256
+ bookmark.time_updated ?? null,
4257
+ JSON.stringify(bookmark.stats),
4258
+ bookmarkedAt
4259
+ );
4260
+ return { ...bookmark, bookmarked_at: bookmarkedAt };
4261
+ });
4262
+ }
4263
+ function importBookmarks(bookmarks) {
4264
+ return withStateDb((db) => {
4265
+ const existingRows = db.prepare("SELECT agent_name, session_id, bookmarked_at FROM bookmarks").all();
4266
+ const existingTimes = new Map(
4267
+ existingRows.map((row) => [
4268
+ `${String(row.agent_name ?? "")}:${String(row.session_id ?? "")}`,
4269
+ Number(row.bookmarked_at ?? 0)
4270
+ ])
4271
+ );
4272
+ const upsert = db.prepare(
4273
+ `
4274
+ INSERT INTO bookmarks(
4275
+ agent_name,
4276
+ session_id,
4277
+ slug,
4278
+ title,
4279
+ directory,
4280
+ time_created,
4281
+ time_updated,
4282
+ stats_json,
4283
+ bookmarked_at
4284
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
4285
+ ON CONFLICT(agent_name, session_id) DO UPDATE SET
4286
+ slug = excluded.slug,
4287
+ title = excluded.title,
4288
+ directory = excluded.directory,
4289
+ time_created = excluded.time_created,
4290
+ time_updated = excluded.time_updated,
4291
+ stats_json = excluded.stats_json
4292
+ `
4293
+ );
4294
+ const write = db.transaction(() => {
4295
+ for (const bookmark of bookmarks) {
4296
+ const key = `${bookmark.agentKey}:${bookmark.sessionId}`;
4297
+ upsert.run(
4298
+ bookmark.agentKey,
4299
+ bookmark.sessionId,
4300
+ bookmark.fullPath,
4301
+ bookmark.title,
4302
+ bookmark.directory,
4303
+ bookmark.time_created,
4304
+ bookmark.time_updated ?? null,
4305
+ JSON.stringify(bookmark.stats),
4306
+ existingTimes.get(key) ?? Date.now()
4307
+ );
4308
+ }
4309
+ });
4310
+ write();
4311
+ const rows = db.prepare(
4312
+ `
4313
+ SELECT
4314
+ agent_name,
4315
+ session_id,
4316
+ slug,
4317
+ title,
4318
+ directory,
4319
+ time_created,
4320
+ time_updated,
4321
+ stats_json,
4322
+ bookmarked_at
4323
+ FROM bookmarks
4324
+ ORDER BY COALESCE(time_updated, time_created) DESC, bookmarked_at DESC
4325
+ `
4326
+ ).all();
4327
+ return rows.map(toBookmarkRecord);
4328
+ });
4329
+ }
4330
+ function deleteBookmark(agentKey, sessionId) {
4331
+ withStateDb((db) => {
4332
+ db.prepare(
4333
+ `
4334
+ DELETE FROM bookmarks
4335
+ WHERE agent_name = ? AND session_id = ?
4336
+ `
4337
+ ).run(agentKey, sessionId);
4338
+ });
4339
+ }
4077
4340
 
4078
4341
  export {
4079
4342
  registerAgent,
@@ -4102,6 +4365,11 @@ export {
4102
4365
  searchSessions,
4103
4366
  filterSessions,
4104
4367
  scanSessions,
4105
- scanSessionsAsync
4368
+ scanSessionsAsync,
4369
+ BookmarkStorageUnavailableError,
4370
+ listBookmarks,
4371
+ upsertBookmark,
4372
+ importBookmarks,
4373
+ deleteBookmark
4106
4374
  };
4107
- //# sourceMappingURL=chunk-UQI7CTEK.js.map
4375
+ //# sourceMappingURL=chunk-BGQXQTWM.js.map