codesesh 0.4.0 → 0.5.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/dist/index.js CHANGED
@@ -1,20 +1,27 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ BookmarkStorageUnavailableError,
4
+ classifySessionTags,
5
+ computeIdentity,
3
6
  createRegisteredAgents,
4
7
  deleteBookmark,
5
8
  filterSessions,
6
9
  getAgentInfoMap,
7
10
  getCursorDataPath,
11
+ getSmartTagSourceTimestamp,
8
12
  importBookmarks,
9
13
  listBookmarks,
14
+ listCachedProjectGroups,
10
15
  perf,
16
+ realFs,
17
+ refreshPricingCache,
11
18
  resolveProviderRoots,
12
19
  saveCachedSessions,
13
20
  scanSessions,
14
21
  searchSessions,
15
22
  syncSessionSearchIndex,
16
23
  upsertBookmark
17
- } from "./chunk-EIIG7J6V.js";
24
+ } from "./chunk-FZNZAMTZ.js";
18
25
 
19
26
  // src/index.ts
20
27
  import { defineCommand, runMain } from "citty";
@@ -23,14 +30,144 @@ import { defineCommand, runMain } from "citty";
23
30
  import { Hono as Hono2 } from "hono";
24
31
  import { serve } from "@hono/node-server";
25
32
  import { serveStatic } from "@hono/node-server/serve-static";
26
- import { logger } from "hono/logger";
27
- import { existsSync } from "fs";
33
+ import { existsSync as existsSync2 } from "fs";
28
34
  import { resolve, dirname } from "path";
29
35
  import { fileURLToPath } from "url";
30
36
 
31
37
  // src/api/routes.ts
32
38
  import { Hono } from "hono";
33
39
 
40
+ // src/logging.ts
41
+ import {
42
+ appendFileSync,
43
+ existsSync,
44
+ mkdirSync,
45
+ readdirSync,
46
+ renameSync,
47
+ statSync,
48
+ unlinkSync
49
+ } from "fs";
50
+ import { homedir } from "os";
51
+ import { join } from "path";
52
+ var LEVEL_WEIGHT = {
53
+ debug: 10,
54
+ info: 20,
55
+ warn: 30,
56
+ error: 40
57
+ };
58
+ function parseLevel(value) {
59
+ if (value === "debug" || value === "info" || value === "warn" || value === "error") {
60
+ return value;
61
+ }
62
+ return "info";
63
+ }
64
+ function parsePositiveInt(value, fallback) {
65
+ const parsed = Number(value);
66
+ return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : fallback;
67
+ }
68
+ function getDefaultLogDir() {
69
+ const base = process.env.XDG_CACHE_HOME ?? join(homedir(), ".cache");
70
+ return join(base, "codesesh", "logs");
71
+ }
72
+ function toLogValue(value, depth = 0) {
73
+ if (value == null || typeof value === "string" || typeof value === "number") return value;
74
+ if (typeof value === "boolean") return value;
75
+ if (typeof value === "bigint") return value.toString();
76
+ if (value instanceof Error) {
77
+ return {
78
+ name: value.name,
79
+ message: value.message,
80
+ stack: value.stack
81
+ };
82
+ }
83
+ if (depth >= 4) return "[truncated]";
84
+ if (Array.isArray(value)) return value.slice(0, 50).map((item) => toLogValue(item, depth + 1));
85
+ if (typeof value === "object") {
86
+ return Object.fromEntries(
87
+ Object.entries(value).map(([key, item]) => [
88
+ key,
89
+ toLogValue(item, depth + 1)
90
+ ])
91
+ );
92
+ }
93
+ return String(value);
94
+ }
95
+ function timestampForFile(date = /* @__PURE__ */ new Date()) {
96
+ return date.toISOString().replace(/[:.]/g, "-");
97
+ }
98
+ var AppLogger = class {
99
+ logDir;
100
+ level;
101
+ maxBytes;
102
+ maxFiles;
103
+ currentPath;
104
+ rotationIndex = 0;
105
+ constructor(options = {}) {
106
+ this.logDir = options.logDir ?? process.env.CODESESH_LOG_DIR ?? getDefaultLogDir();
107
+ this.level = options.level ?? parseLevel(process.env.CODESESH_LOG_LEVEL);
108
+ this.maxBytes = options.maxBytes ?? parsePositiveInt(process.env.CODESESH_LOG_MAX_BYTES, 5e6);
109
+ this.maxFiles = options.maxFiles ?? parsePositiveInt(process.env.CODESESH_LOG_MAX_FILES, 5);
110
+ this.currentPath = join(this.logDir, "codesesh.log");
111
+ }
112
+ getLogPath() {
113
+ return this.currentPath;
114
+ }
115
+ debug(event, data = {}) {
116
+ this.write("debug", event, data);
117
+ }
118
+ info(event, data = {}) {
119
+ this.write("info", event, data);
120
+ }
121
+ warn(event, data = {}) {
122
+ this.write("warn", event, data);
123
+ }
124
+ error(event, data = {}) {
125
+ this.write("error", event, data);
126
+ }
127
+ write(level, event, data) {
128
+ if (LEVEL_WEIGHT[level] < LEVEL_WEIGHT[this.level]) return;
129
+ try {
130
+ mkdirSync(this.logDir, { recursive: true });
131
+ const line = `${JSON.stringify({
132
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
133
+ level,
134
+ event,
135
+ pid: process.pid,
136
+ ...toLogValue(data)
137
+ })}
138
+ `;
139
+ this.rotateIfNeeded(Buffer.byteLength(line));
140
+ appendFileSync(this.currentPath, line, "utf8");
141
+ } catch {
142
+ }
143
+ }
144
+ rotateIfNeeded(nextBytes) {
145
+ if (!existsSync(this.currentPath)) {
146
+ this.removeExpiredLogs();
147
+ return;
148
+ }
149
+ const currentSize = statSync(this.currentPath).size;
150
+ if (currentSize + nextBytes <= this.maxBytes) return;
151
+ this.rotationIndex += 1;
152
+ const rotatedPath = join(
153
+ this.logDir,
154
+ `codesesh-${timestampForFile()}-${process.pid}-${this.rotationIndex}.log`
155
+ );
156
+ renameSync(this.currentPath, rotatedPath);
157
+ this.removeExpiredLogs();
158
+ }
159
+ removeExpiredLogs() {
160
+ const rotated = readdirSync(this.logDir).filter((name) => /^codesesh-.+\.log$/.test(name)).map((name) => {
161
+ const path = join(this.logDir, name);
162
+ return { path, mtimeMs: statSync(path).mtimeMs };
163
+ }).toSorted((a, b) => b.mtimeMs - a.mtimeMs);
164
+ for (const item of rotated.slice(Math.max(0, this.maxFiles - 1))) {
165
+ unlinkSync(item.path);
166
+ }
167
+ }
168
+ };
169
+ var appLogger = new AppLogger();
170
+
34
171
  // src/api/handlers.ts
35
172
  function isRecord(value) {
36
173
  return typeof value === "object" && value !== null;
@@ -78,6 +215,24 @@ function filterSessionsByActivityWindow(sessions, from, to) {
78
215
  return true;
79
216
  });
80
217
  }
218
+ function matchesProjectScope(session, cwd) {
219
+ if (!session.directory) return false;
220
+ const identity = computeIdentity(cwd, realFs);
221
+ if (session.project_identity?.key === identity.key) return true;
222
+ return session.directory.toLowerCase().includes(cwd.toLowerCase());
223
+ }
224
+ function sanitizeClientLogData(value) {
225
+ if (!isRecord(value)) return {};
226
+ return Object.fromEntries(
227
+ Object.entries(value).slice(0, 30).map(([key, item]) => {
228
+ if (typeof item === "string") return [key, item.slice(0, 300)];
229
+ if (typeof item === "number" || typeof item === "boolean" || item == null) {
230
+ return [key, item];
231
+ }
232
+ return [key, String(item).slice(0, 300)];
233
+ })
234
+ );
235
+ }
81
236
  function handleGetConfig(c, defaults) {
82
237
  return c.json({
83
238
  window: {
@@ -99,11 +254,22 @@ function handleGetAgents(c, scanSource, defaults = {}) {
99
254
  const info = getAgentInfoMap(counts);
100
255
  return c.json(info);
101
256
  }
257
+ function handleGetProjects(c, scanSource, defaults = {}) {
258
+ const scanResult = scanSource.getSnapshot();
259
+ const { from, to } = defaults;
260
+ return c.json({
261
+ projects: listCachedProjectGroups(
262
+ filterSessionsByActivityWindow(scanResult.sessions, from, to)
263
+ )
264
+ });
265
+ }
102
266
  function handleGetSessions(c, scanSource, defaults = {}) {
103
267
  const scanResult = scanSource.getSnapshot();
104
268
  const agent = c.req.query("agent");
105
269
  const q = c.req.query("q")?.toLowerCase();
106
- const cwd = c.req.query("cwd")?.toLowerCase();
270
+ const cwd = c.req.query("cwd");
271
+ const projectKey = c.req.query("projectKey");
272
+ const tag = c.req.query("tag")?.toLowerCase();
107
273
  const from = parseDateParam(c.req.query("from"), defaults.from);
108
274
  const to = parseDateParam(c.req.query("to"), defaults.to);
109
275
  let sessions = [];
@@ -112,10 +278,15 @@ function handleGetSessions(c, scanSource, defaults = {}) {
112
278
  } else {
113
279
  sessions = [...scanResult.sessions];
114
280
  }
115
- if (cwd) {
116
- sessions = sessions.filter((s) => s.directory.toLowerCase().includes(cwd));
281
+ if (projectKey) {
282
+ sessions = sessions.filter((s) => s.project_identity?.key === projectKey);
283
+ } else if (cwd) {
284
+ sessions = sessions.filter((s) => matchesProjectScope(s, cwd));
117
285
  }
118
286
  sessions = filterSessionsByActivityWindow(sessions, from, to);
287
+ if (tag) {
288
+ sessions = sessions.filter((s) => s.smart_tags?.includes(tag));
289
+ }
119
290
  if (q) {
120
291
  sessions = sessions.filter((s) => s.title.toLowerCase().includes(q));
121
292
  }
@@ -149,6 +320,7 @@ function handleSearchSessions(c, scanSource, defaults = {}) {
149
320
  return c.json({ results });
150
321
  }
151
322
  async function handleGetSessionData(c, scanSource) {
323
+ const startedAt = performance.now();
152
324
  const scanResult = scanSource.getSnapshot();
153
325
  const agentName = c.req.param("agent");
154
326
  const sessionId = c.req.param("id");
@@ -160,22 +332,71 @@ async function handleGetSessionData(c, scanSource) {
160
332
  return c.json({ error: `Unknown agent: ${agentName}` }, 404);
161
333
  }
162
334
  try {
335
+ const loadStartedAt = performance.now();
163
336
  const data = agent.getSessionData(sessionId);
164
- return c.json(data);
337
+ const loadDuration = performance.now() - loadStartedAt;
338
+ const tagStartedAt = performance.now();
339
+ const smartTags = classifySessionTags(data);
340
+ const tagDuration = performance.now() - tagStartedAt;
341
+ const head = scanResult.byAgent[agentName]?.find((item) => item.id === sessionId);
342
+ appLogger.info("api.session_data", {
343
+ agent: agentName,
344
+ session_id: sessionId,
345
+ messages: data.messages.length,
346
+ load_duration_ms: Math.round(loadDuration),
347
+ tag_duration_ms: Math.round(tagDuration),
348
+ duration_ms: Math.round(performance.now() - startedAt)
349
+ });
350
+ return c.json({
351
+ ...data,
352
+ project_identity: data.project_identity ?? head?.project_identity,
353
+ smart_tags: smartTags,
354
+ smart_tags_source_updated_at: getSmartTagSourceTimestamp(data)
355
+ });
165
356
  } catch (err) {
166
357
  const message = err instanceof Error ? err.message : "Failed to load session";
358
+ appLogger.error("api.session_data.error", {
359
+ agent: agentName,
360
+ session_id: sessionId,
361
+ duration_ms: Math.round(performance.now() - startedAt),
362
+ error: message
363
+ });
167
364
  return c.json({ error: message }, 500);
168
365
  }
169
366
  }
367
+ async function handlePostClientLog(c) {
368
+ const payload = await c.req.json().catch(() => null);
369
+ const rawEvent = payload?.event;
370
+ if (typeof rawEvent !== "string" || !rawEvent.trim()) {
371
+ return c.json({ ok: false }, 400);
372
+ }
373
+ const event = rawEvent.trim().replace(/[^a-zA-Z0-9_.:-]/g, "_").slice(0, 120);
374
+ appLogger.info(`client.${event}`, sanitizeClientLogData(payload?.data));
375
+ return c.json({ ok: true });
376
+ }
170
377
  function handleGetBookmarks(c) {
171
- return c.json({ bookmarks: listBookmarks() });
378
+ try {
379
+ return c.json({ bookmarks: listBookmarks(), storageAvailable: true });
380
+ } catch (error) {
381
+ if (error instanceof BookmarkStorageUnavailableError) {
382
+ return c.json({ bookmarks: [], storageAvailable: false });
383
+ }
384
+ throw error;
385
+ }
172
386
  }
173
387
  async function handlePutBookmark(c) {
174
388
  const payload = parseBookmarkPayload(await c.req.json().catch(() => null));
175
389
  if (!payload) {
176
390
  return c.json({ error: "Invalid bookmark payload" }, 400);
177
391
  }
178
- return c.json({ bookmark: upsertBookmark(payload) });
392
+ try {
393
+ return c.json({ bookmark: upsertBookmark(payload), storageAvailable: true });
394
+ } catch (error) {
395
+ if (error instanceof BookmarkStorageUnavailableError) {
396
+ return c.json({ error: "Bookmark storage is unavailable" }, 503);
397
+ }
398
+ throw error;
399
+ }
179
400
  }
180
401
  async function handleImportBookmarks(c) {
181
402
  const payload = await c.req.json().catch(() => null);
@@ -186,7 +407,14 @@ async function handleImportBookmarks(c) {
186
407
  if (bookmarks.length !== payload.length) {
187
408
  return c.json({ error: "Invalid bookmark payload" }, 400);
188
409
  }
189
- return c.json({ bookmarks: importBookmarks(bookmarks) });
410
+ try {
411
+ return c.json({ bookmarks: importBookmarks(bookmarks), storageAvailable: true });
412
+ } catch (error) {
413
+ if (error instanceof BookmarkStorageUnavailableError) {
414
+ return c.json({ error: "Bookmark storage is unavailable" }, 503);
415
+ }
416
+ throw error;
417
+ }
190
418
  }
191
419
  function handleDeleteBookmark(c) {
192
420
  const agentKey = c.req.param("agent");
@@ -194,8 +422,15 @@ function handleDeleteBookmark(c) {
194
422
  if (!agentKey || !sessionId) {
195
423
  return c.json({ error: "Missing bookmark identifier" }, 400);
196
424
  }
197
- deleteBookmark(agentKey, sessionId);
198
- return c.json({ ok: true });
425
+ try {
426
+ deleteBookmark(agentKey, sessionId);
427
+ return c.json({ ok: true, storageAvailable: true });
428
+ } catch (error) {
429
+ if (error instanceof BookmarkStorageUnavailableError) {
430
+ return c.json({ error: "Bookmark storage is unavailable" }, 503);
431
+ }
432
+ throw error;
433
+ }
199
434
  }
200
435
  function toLocalDateKey(ts) {
201
436
  const d = new Date(ts);
@@ -211,23 +446,22 @@ function startOfLocalDay(ts) {
211
446
  }
212
447
  function resolveDashboardWindow(defaults, queryDays, queryFrom, queryTo) {
213
448
  const now = Date.now();
214
- const todayStart = startOfLocalDay(now);
215
- const toTs = parseDateParam(queryTo, defaults.to) ?? todayStart + 24 * 60 * 60 * 1e3 - 1;
449
+ const toTs = parseDateParam(queryTo, defaults.to) ?? now;
216
450
  const parsedDays = queryDays ? parseInt(queryDays, 10) : NaN;
217
451
  let days = Number.isFinite(parsedDays) && parsedDays > 0 ? parsedDays : defaults.days;
218
452
  const fromFromQuery = parseDateParam(queryFrom, void 0);
219
453
  let fromTs;
220
454
  if (fromFromQuery != null) {
221
- fromTs = startOfLocalDay(fromFromQuery);
222
- days ??= Math.max(1, Math.ceil((todayStart - fromTs) / 864e5) + 1);
223
- } else if (days && days > 0) {
224
- fromTs = todayStart - (days - 1) * 864e5;
455
+ fromTs = fromFromQuery;
456
+ days ??= Math.max(1, Math.ceil((toTs - fromTs) / 864e5));
225
457
  } else if (defaults.from != null) {
226
- fromTs = startOfLocalDay(defaults.from);
227
- days = Math.max(1, Math.ceil((todayStart - fromTs) / 864e5) + 1);
458
+ fromTs = defaults.from;
459
+ days ??= Math.max(1, Math.ceil((toTs - fromTs) / 864e5));
460
+ } else if (days && days > 0) {
461
+ fromTs = startOfLocalDay(toTs) - (days - 1) * 864e5;
228
462
  } else {
229
463
  days = 30;
230
- fromTs = todayStart - (days - 1) * 864e5;
464
+ fromTs = startOfLocalDay(toTs) - (days - 1) * 864e5;
231
465
  }
232
466
  return { from: fromTs, to: toTs, days };
233
467
  }
@@ -252,11 +486,13 @@ function handleGetDashboard(c, scanSource, defaults = {}) {
252
486
  let totalMessages = 0;
253
487
  let totalTokens = 0;
254
488
  let totalCost = 0;
489
+ let hasEstimatedCost = false;
255
490
  let latestActivity = 0;
256
491
  for (const session of windowed) {
257
492
  totalMessages += session.stats.message_count;
258
493
  totalTokens += getTotalTokens(session.stats);
259
494
  totalCost += session.stats.total_cost ?? 0;
495
+ if (session.stats.cost_source === "estimated") hasEstimatedCost = true;
260
496
  const activity = getSessionActivityTime(session);
261
497
  if (activity > latestActivity) latestActivity = activity;
262
498
  }
@@ -281,7 +517,8 @@ function handleGetDashboard(c, scanSource, defaults = {}) {
281
517
  const dailyMap = /* @__PURE__ */ new Map();
282
518
  const dailyTokenMap = /* @__PURE__ */ new Map();
283
519
  const bucketStart = startOfLocalDay(from);
284
- for (let i = 0; i < days; i += 1) {
520
+ const bucketDays = Math.floor((startOfLocalDay(to) - bucketStart) / 864e5) + 1;
521
+ for (let i = 0; i < bucketDays; i += 1) {
285
522
  const ts = bucketStart + i * 864e5;
286
523
  const key = toLocalDateKey(ts);
287
524
  dailyMap.set(key, { date: key, sessions: 0, messages: 0 });
@@ -330,6 +567,7 @@ function handleGetDashboard(c, scanSource, defaults = {}) {
330
567
  messages: totalMessages,
331
568
  tokens: totalTokens,
332
569
  cost: totalCost,
570
+ cost_source: totalCost > 0 ? hasEstimatedCost ? "estimated" : "recorded" : void 0,
333
571
  latestActivity: latestActivity || void 0
334
572
  },
335
573
  perAgent,
@@ -391,6 +629,7 @@ function createApiRoutes(scanSource, store, options = {}) {
391
629
  };
392
630
  api.get("/config", (c) => handleGetConfig(c, listDefaults));
393
631
  api.get("/agents", (c) => handleGetAgents(c, scanSource, listDefaults));
632
+ api.get("/projects", (c) => handleGetProjects(c, scanSource, listDefaults));
394
633
  api.get("/sessions", (c) => handleGetSessions(c, scanSource, listDefaults));
395
634
  api.get("/search", (c) => handleSearchSessions(c, scanSource, listDefaults));
396
635
  api.get("/sessions/:agent/:id", (c) => handleGetSessionData(c, scanSource));
@@ -399,6 +638,7 @@ function createApiRoutes(scanSource, store, options = {}) {
399
638
  api.put("/bookmarks", (c) => handlePutBookmark(c));
400
639
  api.post("/bookmarks/import", (c) => handleImportBookmarks(c));
401
640
  api.delete("/bookmarks/:agent/:id", (c) => handleDeleteBookmark(c));
641
+ api.post("/logs", (c) => handlePostClientLog(c));
402
642
  if (store) {
403
643
  api.get("/events", (c) => createSseResponse(store, c.req.raw.signal));
404
644
  }
@@ -409,11 +649,11 @@ function createApiRoutes(scanSource, store, options = {}) {
409
649
  function findWebDistPath() {
410
650
  const __dirname2 = dirname(fileURLToPath(import.meta.url));
411
651
  const packagedPath = resolve(__dirname2, "web");
412
- if (existsSync(packagedPath)) {
652
+ if (existsSync2(packagedPath)) {
413
653
  return packagedPath;
414
654
  }
415
655
  const devPath = resolve(__dirname2, "../../../apps/web/dist");
416
- if (existsSync(devPath)) {
656
+ if (existsSync2(devPath)) {
417
657
  return devPath;
418
658
  }
419
659
  return null;
@@ -440,7 +680,26 @@ function getServerStartupErrorMessage(error, port) {
440
680
  }
441
681
  async function createServer(port, store, options = {}) {
442
682
  const app = new Hono2();
443
- app.use("*", logger());
683
+ app.use("*", async (c, next) => {
684
+ const startedAt = performance.now();
685
+ let thrown;
686
+ try {
687
+ await next();
688
+ } catch (error) {
689
+ thrown = error;
690
+ throw error;
691
+ } finally {
692
+ const url2 = new URL(c.req.url);
693
+ appLogger.info("http.request", {
694
+ method: c.req.method,
695
+ path: url2.pathname,
696
+ query_keys: [...url2.searchParams.keys()].toSorted(),
697
+ status: c.res.status,
698
+ duration_ms: Math.round(performance.now() - startedAt),
699
+ error: thrown instanceof Error ? thrown.message : void 0
700
+ });
701
+ }
702
+ });
444
703
  const routeOptions = {
445
704
  defaultSessionFrom: options.defaultSessionFrom,
446
705
  defaultSessionTo: options.defaultSessionTo,
@@ -463,6 +722,7 @@ async function createServer(port, store, options = {}) {
463
722
  try {
464
723
  await waitForListening(server);
465
724
  } catch (error) {
725
+ appLogger.error("server.listen.error", { port, error });
466
726
  server.close();
467
727
  if (store.shutdown) {
468
728
  await store.shutdown();
@@ -470,9 +730,11 @@ async function createServer(port, store, options = {}) {
470
730
  throw new Error(getServerStartupErrorMessage(error, port));
471
731
  }
472
732
  const url = `http://localhost:${port}`;
733
+ appLogger.info("server.listen", { port, url });
473
734
  return {
474
735
  url,
475
736
  shutdown: () => {
737
+ appLogger.info("server.shutdown", { port });
476
738
  server.close();
477
739
  if (store.shutdown) {
478
740
  void store.shutdown();
@@ -482,8 +744,8 @@ async function createServer(port, store, options = {}) {
482
744
  }
483
745
 
484
746
  // src/live-scan.ts
485
- import { existsSync as existsSync2 } from "fs";
486
- import { dirname as dirname2, isAbsolute, join } from "path";
747
+ import { existsSync as existsSync3 } from "fs";
748
+ import { dirname as dirname2, isAbsolute, join as join2 } from "path";
487
749
  import chokidar from "chokidar";
488
750
  function sortSessions(sessions) {
489
751
  return [...sessions].sort(
@@ -547,11 +809,11 @@ function buildUpdateEvent(agentName, previousSessions, nextSessions) {
547
809
  };
548
810
  }
549
811
  function closestWatchablePath(targetPath) {
550
- if (!isAbsolute(targetPath) && !existsSync2(targetPath)) {
812
+ if (!isAbsolute(targetPath) && !existsSync3(targetPath)) {
551
813
  return null;
552
814
  }
553
815
  let current = targetPath;
554
- while (!existsSync2(current)) {
816
+ while (!existsSync3(current)) {
555
817
  const parent = dirname2(current);
556
818
  if (parent === current) {
557
819
  return null;
@@ -566,24 +828,24 @@ function resolveAgentWatchTargets(agentName) {
566
828
  switch (agentName) {
567
829
  case "claudecode":
568
830
  return [
569
- { path: join(roots.claudeRoot, "projects"), depth: 2 },
831
+ { path: join2(roots.claudeRoot, "projects"), depth: 2 },
570
832
  { path: "data/claudecode", depth: 2 }
571
833
  ];
572
834
  case "codex":
573
- return [{ path: join(roots.codexRoot, "sessions"), depth: 4 }];
835
+ return [{ path: join2(roots.codexRoot, "sessions"), depth: 4 }];
574
836
  case "cursor":
575
837
  return cursorDataPath ? [
576
- { path: join(cursorDataPath, "globalStorage", "state.vscdb") },
577
- { path: join(cursorDataPath, "workspaceStorage"), depth: 2 }
838
+ { path: join2(cursorDataPath, "globalStorage", "state.vscdb") },
839
+ { path: join2(cursorDataPath, "workspaceStorage"), depth: 2 }
578
840
  ] : [];
579
841
  case "kimi":
580
842
  return [
581
- { path: join(roots.kimiRoot, "sessions"), depth: 2 },
843
+ { path: join2(roots.kimiRoot, "sessions"), depth: 2 },
582
844
  { path: "data/kimi", depth: 2 }
583
845
  ];
584
846
  case "opencode":
585
847
  return [
586
- { path: join(roots.opencodeRoot, "opencode.db") },
848
+ { path: join2(roots.opencodeRoot, "opencode.db") },
587
849
  { path: "data/opencode/opencode.db" }
588
850
  ];
589
851
  default:
@@ -591,12 +853,14 @@ function resolveAgentWatchTargets(agentName) {
591
853
  }
592
854
  }
593
855
  var LiveScanStore = class {
594
- constructor(watchEnabled = true, scanOptions = {}) {
856
+ constructor(watchEnabled = true, scanOptions = {}, startupScanOptions = {}) {
595
857
  this.watchEnabled = watchEnabled;
596
858
  this.scanOptions = scanOptions;
859
+ this.startupScanOptions = startupScanOptions;
597
860
  }
598
861
  watchEnabled;
599
862
  scanOptions;
863
+ startupScanOptions;
600
864
  agents = [];
601
865
  byAgent = {};
602
866
  sessions = [];
@@ -607,33 +871,30 @@ var LiveScanStore = class {
607
871
  pendingRefreshes = /* @__PURE__ */ new Set();
608
872
  watchers = [];
609
873
  async initialize() {
874
+ const startedAt = performance.now();
875
+ appLogger.info("scan.initial.start", {
876
+ watch_enabled: this.watchEnabled,
877
+ agents: this.scanOptions.agents,
878
+ use_cache: this.scanOptions.useCache ?? true,
879
+ startup_from: this.startupScanOptions.from,
880
+ startup_to: this.startupScanOptions.to
881
+ });
610
882
  const initialResult = await scanSessions({
611
883
  ...this.scanOptions,
612
- useCache: true,
613
- smartRefresh: false
884
+ ...this.startupScanOptions,
885
+ useCache: this.scanOptions.useCache ?? true,
886
+ smartRefresh: false,
887
+ writeCache: this.startupScanOptions.from != null || this.startupScanOptions.to != null ? false : void 0,
888
+ includeSmartTags: this.startupScanOptions.from != null || this.startupScanOptions.to != null ? false : void 0
614
889
  });
615
- const knownAgents = createRegisteredAgents();
616
- const agentMap = /* @__PURE__ */ new Map();
617
- const allowedAgents = this.getAllowedAgents();
618
- for (const agent of initialResult.agents) {
619
- agentMap.set(agent.name, agent);
620
- }
621
- for (const agent of knownAgents) {
622
- if (!agentMap.has(agent.name)) {
623
- agentMap.set(agent.name, agent);
624
- }
625
- }
626
- this.agents = [...agentMap.values()].filter((agent) => {
627
- if (!allowedAgents) {
628
- return true;
629
- }
630
- return allowedAgents.has(agent.name.toLowerCase());
890
+ this.applyScanResult(initialResult);
891
+ appLogger.info("scan.initial.done", {
892
+ duration_ms: Math.round(performance.now() - startedAt),
893
+ sessions: this.sessions.length,
894
+ agents: Object.fromEntries(
895
+ Object.entries(this.byAgent).map(([key, value]) => [key, value.length])
896
+ )
631
897
  });
632
- for (const agent of this.agents) {
633
- this.byAgent[agent.name] = sortSessions(initialResult.byAgent[agent.name] ?? []);
634
- this.refreshTimestamps.set(agent.name, Date.now());
635
- }
636
- this.rebuildSessions();
637
898
  if (this.watchEnabled) {
638
899
  this.startWatching();
639
900
  }
@@ -667,6 +928,34 @@ var LiveScanStore = class {
667
928
  rebuildSessions() {
668
929
  this.sessions = sortSessions(Object.values(this.byAgent).flat());
669
930
  }
931
+ hasStartupWindow() {
932
+ return this.startupScanOptions.from != null || this.startupScanOptions.to != null;
933
+ }
934
+ applyScanResult(result) {
935
+ const knownAgents = createRegisteredAgents();
936
+ const agentMap = /* @__PURE__ */ new Map();
937
+ const allowedAgents = this.getAllowedAgents();
938
+ for (const agent of result.agents) {
939
+ agentMap.set(agent.name, agent);
940
+ }
941
+ for (const agent of knownAgents) {
942
+ if (!agentMap.has(agent.name)) {
943
+ agentMap.set(agent.name, agent);
944
+ }
945
+ }
946
+ this.agents = [...agentMap.values()].filter((agent) => {
947
+ if (!allowedAgents) {
948
+ return true;
949
+ }
950
+ return allowedAgents.has(agent.name.toLowerCase());
951
+ });
952
+ this.byAgent = {};
953
+ for (const agent of this.agents) {
954
+ this.byAgent[agent.name] = sortSessions(result.byAgent[agent.name] ?? []);
955
+ this.refreshTimestamps.set(agent.name, Date.now());
956
+ }
957
+ this.rebuildSessions();
958
+ }
670
959
  getAllowedAgents() {
671
960
  if (!this.scanOptions.agents?.length) {
672
961
  return null;
@@ -674,7 +963,7 @@ var LiveScanStore = class {
674
963
  return new Set(this.scanOptions.agents.map((agent) => agent.toLowerCase()));
675
964
  }
676
965
  applyFilters(sessions) {
677
- return filterSessions(sessions, this.scanOptions);
966
+ return filterSessions(sessions, { ...this.scanOptions, ...this.startupScanOptions });
678
967
  }
679
968
  startWatching() {
680
969
  for (const agent of this.agents) {
@@ -686,8 +975,16 @@ var LiveScanStore = class {
686
975
  (target, index, items) => items.findIndex((item) => item.path === target.path && item.depth === target.depth) === index
687
976
  );
688
977
  if (watchTargets.length === 0) {
978
+ appLogger.debug("watch.skip", { agent: agent.name });
689
979
  continue;
690
980
  }
981
+ appLogger.info("watch.start", {
982
+ agent: agent.name,
983
+ targets: watchTargets.map((target) => ({
984
+ path: target.path,
985
+ depth: target.depth ?? 0
986
+ }))
987
+ });
691
988
  const watcher = chokidar.watch(
692
989
  watchTargets.map((target) => target.path),
693
990
  {
@@ -706,12 +1003,14 @@ var LiveScanStore = class {
706
1003
  this.scheduleRefresh(agent.name);
707
1004
  });
708
1005
  watcher.on("error", (error) => {
1006
+ appLogger.error("watch.error", { agent: agent.name, error });
709
1007
  console.error(`[${agent.name}] File watcher failed:`, error);
710
1008
  });
711
1009
  this.watchers.push(watcher);
712
1010
  }
713
1011
  }
714
1012
  scheduleRefresh(agentName, delayMs = 200) {
1013
+ appLogger.debug("scan.refresh.schedule", { agent: agentName, delay_ms: delayMs });
715
1014
  const existing = this.refreshTimers.get(agentName);
716
1015
  if (existing) {
717
1016
  clearTimeout(existing);
@@ -724,6 +1023,7 @@ var LiveScanStore = class {
724
1023
  }
725
1024
  async refreshAgent(agentName) {
726
1025
  if (this.refreshInFlight.has(agentName)) {
1026
+ appLogger.debug("scan.refresh.pending", { agent: agentName });
727
1027
  this.pendingRefreshes.add(agentName);
728
1028
  return;
729
1029
  }
@@ -738,8 +1038,10 @@ var LiveScanStore = class {
738
1038
  }
739
1039
  }
740
1040
  async runRefresh(agentName) {
1041
+ const startedAt = performance.now();
741
1042
  const agent = this.agents.find((item) => item.name === agentName);
742
1043
  if (!agent) {
1044
+ appLogger.warn("scan.refresh.missing_agent", { agent: agentName });
743
1045
  return;
744
1046
  }
745
1047
  const previousSessions = this.byAgent[agentName] ?? [];
@@ -753,17 +1055,23 @@ var LiveScanStore = class {
753
1055
  );
754
1056
  this.refreshTimestamps.set(agentName, checkResult.timestamp);
755
1057
  if (!checkResult.hasChanges) {
1058
+ appLogger.debug("scan.refresh.unchanged", {
1059
+ agent: agentName,
1060
+ duration_ms: Math.round(performance.now() - startedAt)
1061
+ });
756
1062
  return;
757
1063
  }
758
1064
  nextSessions = await Promise.resolve(
759
1065
  agent.incrementalScan(previousSessions, checkResult.changedIds ?? [])
760
1066
  );
761
1067
  } else {
762
- nextSessions = await Promise.resolve(agent.scan());
1068
+ nextSessions = await Promise.resolve(agent.scan(this.startupScanOptions));
763
1069
  this.refreshTimestamps.set(agentName, Date.now());
764
1070
  }
765
1071
  nextSessions = this.applyFilters(nextSessions);
766
- saveCachedSessions(agentName, nextSessions, buildAgentCacheMeta(agent));
1072
+ if (!this.hasStartupWindow()) {
1073
+ saveCachedSessions(agentName, nextSessions, buildAgentCacheMeta(agent));
1074
+ }
767
1075
  syncSessionSearchIndex(agentName, nextSessions, (sessionId) => agent.getSessionData(sessionId));
768
1076
  const event = buildUpdateEvent(agentName, previousSessions, nextSessions);
769
1077
  this.byAgent[agentName] = sortSessions(nextSessions);
@@ -772,6 +1080,14 @@ var LiveScanStore = class {
772
1080
  event.totalSessions = this.sessions.length;
773
1081
  this.emit(event);
774
1082
  }
1083
+ appLogger.info("scan.refresh.done", {
1084
+ agent: agentName,
1085
+ duration_ms: Math.round(performance.now() - startedAt),
1086
+ sessions: nextSessions.length,
1087
+ new_sessions: event?.newSessions ?? 0,
1088
+ updated_sessions: event?.updatedSessions ?? 0,
1089
+ removed_sessions: event?.removedSessions ?? 0
1090
+ });
775
1091
  }
776
1092
  };
777
1093
 
@@ -907,6 +1223,7 @@ var main = defineCommand({
907
1223
  }
908
1224
  },
909
1225
  async run({ args }) {
1226
+ const startedAt = performance.now();
910
1227
  const port = parseInt(args.port, 10) || 4321;
911
1228
  const noOpen = args.noOpen;
912
1229
  const jsonOnly = args.json;
@@ -916,11 +1233,22 @@ var main = defineCommand({
916
1233
  if (trace) {
917
1234
  perf.enable();
918
1235
  }
1236
+ appLogger.info("cli.start", {
1237
+ version: VERSION,
1238
+ argv: process.argv.slice(2),
1239
+ port,
1240
+ json: jsonOnly,
1241
+ no_open: noOpen,
1242
+ cache: useCache,
1243
+ log_path: appLogger.getLogPath()
1244
+ });
919
1245
  if (clearCache) {
920
- const { clearCache: clear } = await import("./dist-2YXXOCZJ.js");
1246
+ const { clearCache: clear } = await import("./dist-DMEDEJ2D.js");
921
1247
  clear();
1248
+ appLogger.info("cache.clear");
922
1249
  console.log("Cache cleared.");
923
1250
  }
1251
+ void refreshPricingCache();
924
1252
  let targetSession = null;
925
1253
  if (args.session) {
926
1254
  targetSession = parseSessionUri(args.session);
@@ -950,9 +1278,19 @@ var main = defineCommand({
950
1278
  cwd: cwdFilter,
951
1279
  useCache
952
1280
  };
953
- const store = new LiveScanStore(!jsonOnly, scanOptions);
1281
+ const startupScanOptions = targetSession || jsonOnly ? {} : { from: listDefaultFrom, to: listDefaultTo };
1282
+ const store = new LiveScanStore(!jsonOnly, scanOptions, startupScanOptions);
954
1283
  await store.initialize();
955
1284
  const result = store.getSnapshot();
1285
+ appLogger.info("cli.scan_ready", {
1286
+ duration_ms: Math.round(performance.now() - startedAt),
1287
+ sessions: result.sessions.length,
1288
+ agents: Object.fromEntries(
1289
+ Object.entries(result.byAgent).map(([key, value]) => [key, value.length])
1290
+ ),
1291
+ startup_from: startupScanOptions.from,
1292
+ startup_to: startupScanOptions.to
1293
+ });
956
1294
  if (trace) {
957
1295
  console.log(perf.getReport());
958
1296
  }
@@ -975,6 +1313,10 @@ var main = defineCommand({
975
1313
  })),
976
1314
  sessions: windowed
977
1315
  };
1316
+ appLogger.info("cli.json_output", {
1317
+ sessions: windowed.length,
1318
+ duration_ms: Math.round(performance.now() - startedAt)
1319
+ });
978
1320
  console.log(JSON.stringify(output, null, 2));
979
1321
  return;
980
1322
  }
@@ -993,9 +1335,15 @@ var main = defineCommand({
993
1335
  }
994
1336
  console.log(` ${url}`);
995
1337
  console.log("");
1338
+ appLogger.info("cli.ready", {
1339
+ url,
1340
+ duration_ms: Math.round(performance.now() - startedAt),
1341
+ log_path: appLogger.getLogPath()
1342
+ });
996
1343
  if (!noOpen) {
997
1344
  const open = (await import("open")).default;
998
1345
  const targetUrl = targetSession ? `${url}/${targetSession.agent.toLowerCase()}/${targetSession.sessionId}` : url;
1346
+ appLogger.info("browser.open", { url: targetUrl });
999
1347
  await open(targetUrl);
1000
1348
  }
1001
1349
  }