codesesh 0.4.1 → 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,21 +1,27 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  BookmarkStorageUnavailableError,
4
+ classifySessionTags,
5
+ computeIdentity,
4
6
  createRegisteredAgents,
5
7
  deleteBookmark,
6
8
  filterSessions,
7
9
  getAgentInfoMap,
8
10
  getCursorDataPath,
11
+ getSmartTagSourceTimestamp,
9
12
  importBookmarks,
10
13
  listBookmarks,
14
+ listCachedProjectGroups,
11
15
  perf,
16
+ realFs,
17
+ refreshPricingCache,
12
18
  resolveProviderRoots,
13
19
  saveCachedSessions,
14
20
  scanSessions,
15
21
  searchSessions,
16
22
  syncSessionSearchIndex,
17
23
  upsertBookmark
18
- } from "./chunk-BGQXQTWM.js";
24
+ } from "./chunk-FZNZAMTZ.js";
19
25
 
20
26
  // src/index.ts
21
27
  import { defineCommand, runMain } from "citty";
@@ -24,14 +30,144 @@ import { defineCommand, runMain } from "citty";
24
30
  import { Hono as Hono2 } from "hono";
25
31
  import { serve } from "@hono/node-server";
26
32
  import { serveStatic } from "@hono/node-server/serve-static";
27
- import { logger } from "hono/logger";
28
- import { existsSync } from "fs";
33
+ import { existsSync as existsSync2 } from "fs";
29
34
  import { resolve, dirname } from "path";
30
35
  import { fileURLToPath } from "url";
31
36
 
32
37
  // src/api/routes.ts
33
38
  import { Hono } from "hono";
34
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
+
35
171
  // src/api/handlers.ts
36
172
  function isRecord(value) {
37
173
  return typeof value === "object" && value !== null;
@@ -79,6 +215,24 @@ function filterSessionsByActivityWindow(sessions, from, to) {
79
215
  return true;
80
216
  });
81
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
+ }
82
236
  function handleGetConfig(c, defaults) {
83
237
  return c.json({
84
238
  window: {
@@ -100,11 +254,22 @@ function handleGetAgents(c, scanSource, defaults = {}) {
100
254
  const info = getAgentInfoMap(counts);
101
255
  return c.json(info);
102
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
+ }
103
266
  function handleGetSessions(c, scanSource, defaults = {}) {
104
267
  const scanResult = scanSource.getSnapshot();
105
268
  const agent = c.req.query("agent");
106
269
  const q = c.req.query("q")?.toLowerCase();
107
- 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();
108
273
  const from = parseDateParam(c.req.query("from"), defaults.from);
109
274
  const to = parseDateParam(c.req.query("to"), defaults.to);
110
275
  let sessions = [];
@@ -113,10 +278,15 @@ function handleGetSessions(c, scanSource, defaults = {}) {
113
278
  } else {
114
279
  sessions = [...scanResult.sessions];
115
280
  }
116
- if (cwd) {
117
- 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));
118
285
  }
119
286
  sessions = filterSessionsByActivityWindow(sessions, from, to);
287
+ if (tag) {
288
+ sessions = sessions.filter((s) => s.smart_tags?.includes(tag));
289
+ }
120
290
  if (q) {
121
291
  sessions = sessions.filter((s) => s.title.toLowerCase().includes(q));
122
292
  }
@@ -150,6 +320,7 @@ function handleSearchSessions(c, scanSource, defaults = {}) {
150
320
  return c.json({ results });
151
321
  }
152
322
  async function handleGetSessionData(c, scanSource) {
323
+ const startedAt = performance.now();
153
324
  const scanResult = scanSource.getSnapshot();
154
325
  const agentName = c.req.param("agent");
155
326
  const sessionId = c.req.param("id");
@@ -161,13 +332,48 @@ async function handleGetSessionData(c, scanSource) {
161
332
  return c.json({ error: `Unknown agent: ${agentName}` }, 404);
162
333
  }
163
334
  try {
335
+ const loadStartedAt = performance.now();
164
336
  const data = agent.getSessionData(sessionId);
165
- 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
+ });
166
356
  } catch (err) {
167
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
+ });
168
364
  return c.json({ error: message }, 500);
169
365
  }
170
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
+ }
171
377
  function handleGetBookmarks(c) {
172
378
  try {
173
379
  return c.json({ bookmarks: listBookmarks(), storageAvailable: true });
@@ -240,23 +446,22 @@ function startOfLocalDay(ts) {
240
446
  }
241
447
  function resolveDashboardWindow(defaults, queryDays, queryFrom, queryTo) {
242
448
  const now = Date.now();
243
- const todayStart = startOfLocalDay(now);
244
- const toTs = parseDateParam(queryTo, defaults.to) ?? todayStart + 24 * 60 * 60 * 1e3 - 1;
449
+ const toTs = parseDateParam(queryTo, defaults.to) ?? now;
245
450
  const parsedDays = queryDays ? parseInt(queryDays, 10) : NaN;
246
451
  let days = Number.isFinite(parsedDays) && parsedDays > 0 ? parsedDays : defaults.days;
247
452
  const fromFromQuery = parseDateParam(queryFrom, void 0);
248
453
  let fromTs;
249
454
  if (fromFromQuery != null) {
250
- fromTs = startOfLocalDay(fromFromQuery);
251
- days ??= Math.max(1, Math.ceil((todayStart - fromTs) / 864e5) + 1);
252
- } else if (days && days > 0) {
253
- fromTs = todayStart - (days - 1) * 864e5;
455
+ fromTs = fromFromQuery;
456
+ days ??= Math.max(1, Math.ceil((toTs - fromTs) / 864e5));
254
457
  } else if (defaults.from != null) {
255
- fromTs = startOfLocalDay(defaults.from);
256
- 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;
257
462
  } else {
258
463
  days = 30;
259
- fromTs = todayStart - (days - 1) * 864e5;
464
+ fromTs = startOfLocalDay(toTs) - (days - 1) * 864e5;
260
465
  }
261
466
  return { from: fromTs, to: toTs, days };
262
467
  }
@@ -281,11 +486,13 @@ function handleGetDashboard(c, scanSource, defaults = {}) {
281
486
  let totalMessages = 0;
282
487
  let totalTokens = 0;
283
488
  let totalCost = 0;
489
+ let hasEstimatedCost = false;
284
490
  let latestActivity = 0;
285
491
  for (const session of windowed) {
286
492
  totalMessages += session.stats.message_count;
287
493
  totalTokens += getTotalTokens(session.stats);
288
494
  totalCost += session.stats.total_cost ?? 0;
495
+ if (session.stats.cost_source === "estimated") hasEstimatedCost = true;
289
496
  const activity = getSessionActivityTime(session);
290
497
  if (activity > latestActivity) latestActivity = activity;
291
498
  }
@@ -310,7 +517,8 @@ function handleGetDashboard(c, scanSource, defaults = {}) {
310
517
  const dailyMap = /* @__PURE__ */ new Map();
311
518
  const dailyTokenMap = /* @__PURE__ */ new Map();
312
519
  const bucketStart = startOfLocalDay(from);
313
- 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) {
314
522
  const ts = bucketStart + i * 864e5;
315
523
  const key = toLocalDateKey(ts);
316
524
  dailyMap.set(key, { date: key, sessions: 0, messages: 0 });
@@ -359,6 +567,7 @@ function handleGetDashboard(c, scanSource, defaults = {}) {
359
567
  messages: totalMessages,
360
568
  tokens: totalTokens,
361
569
  cost: totalCost,
570
+ cost_source: totalCost > 0 ? hasEstimatedCost ? "estimated" : "recorded" : void 0,
362
571
  latestActivity: latestActivity || void 0
363
572
  },
364
573
  perAgent,
@@ -420,6 +629,7 @@ function createApiRoutes(scanSource, store, options = {}) {
420
629
  };
421
630
  api.get("/config", (c) => handleGetConfig(c, listDefaults));
422
631
  api.get("/agents", (c) => handleGetAgents(c, scanSource, listDefaults));
632
+ api.get("/projects", (c) => handleGetProjects(c, scanSource, listDefaults));
423
633
  api.get("/sessions", (c) => handleGetSessions(c, scanSource, listDefaults));
424
634
  api.get("/search", (c) => handleSearchSessions(c, scanSource, listDefaults));
425
635
  api.get("/sessions/:agent/:id", (c) => handleGetSessionData(c, scanSource));
@@ -428,6 +638,7 @@ function createApiRoutes(scanSource, store, options = {}) {
428
638
  api.put("/bookmarks", (c) => handlePutBookmark(c));
429
639
  api.post("/bookmarks/import", (c) => handleImportBookmarks(c));
430
640
  api.delete("/bookmarks/:agent/:id", (c) => handleDeleteBookmark(c));
641
+ api.post("/logs", (c) => handlePostClientLog(c));
431
642
  if (store) {
432
643
  api.get("/events", (c) => createSseResponse(store, c.req.raw.signal));
433
644
  }
@@ -438,11 +649,11 @@ function createApiRoutes(scanSource, store, options = {}) {
438
649
  function findWebDistPath() {
439
650
  const __dirname2 = dirname(fileURLToPath(import.meta.url));
440
651
  const packagedPath = resolve(__dirname2, "web");
441
- if (existsSync(packagedPath)) {
652
+ if (existsSync2(packagedPath)) {
442
653
  return packagedPath;
443
654
  }
444
655
  const devPath = resolve(__dirname2, "../../../apps/web/dist");
445
- if (existsSync(devPath)) {
656
+ if (existsSync2(devPath)) {
446
657
  return devPath;
447
658
  }
448
659
  return null;
@@ -469,7 +680,26 @@ function getServerStartupErrorMessage(error, port) {
469
680
  }
470
681
  async function createServer(port, store, options = {}) {
471
682
  const app = new Hono2();
472
- 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
+ });
473
703
  const routeOptions = {
474
704
  defaultSessionFrom: options.defaultSessionFrom,
475
705
  defaultSessionTo: options.defaultSessionTo,
@@ -492,6 +722,7 @@ async function createServer(port, store, options = {}) {
492
722
  try {
493
723
  await waitForListening(server);
494
724
  } catch (error) {
725
+ appLogger.error("server.listen.error", { port, error });
495
726
  server.close();
496
727
  if (store.shutdown) {
497
728
  await store.shutdown();
@@ -499,9 +730,11 @@ async function createServer(port, store, options = {}) {
499
730
  throw new Error(getServerStartupErrorMessage(error, port));
500
731
  }
501
732
  const url = `http://localhost:${port}`;
733
+ appLogger.info("server.listen", { port, url });
502
734
  return {
503
735
  url,
504
736
  shutdown: () => {
737
+ appLogger.info("server.shutdown", { port });
505
738
  server.close();
506
739
  if (store.shutdown) {
507
740
  void store.shutdown();
@@ -511,8 +744,8 @@ async function createServer(port, store, options = {}) {
511
744
  }
512
745
 
513
746
  // src/live-scan.ts
514
- import { existsSync as existsSync2 } from "fs";
515
- 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";
516
749
  import chokidar from "chokidar";
517
750
  function sortSessions(sessions) {
518
751
  return [...sessions].sort(
@@ -576,11 +809,11 @@ function buildUpdateEvent(agentName, previousSessions, nextSessions) {
576
809
  };
577
810
  }
578
811
  function closestWatchablePath(targetPath) {
579
- if (!isAbsolute(targetPath) && !existsSync2(targetPath)) {
812
+ if (!isAbsolute(targetPath) && !existsSync3(targetPath)) {
580
813
  return null;
581
814
  }
582
815
  let current = targetPath;
583
- while (!existsSync2(current)) {
816
+ while (!existsSync3(current)) {
584
817
  const parent = dirname2(current);
585
818
  if (parent === current) {
586
819
  return null;
@@ -595,24 +828,24 @@ function resolveAgentWatchTargets(agentName) {
595
828
  switch (agentName) {
596
829
  case "claudecode":
597
830
  return [
598
- { path: join(roots.claudeRoot, "projects"), depth: 2 },
831
+ { path: join2(roots.claudeRoot, "projects"), depth: 2 },
599
832
  { path: "data/claudecode", depth: 2 }
600
833
  ];
601
834
  case "codex":
602
- return [{ path: join(roots.codexRoot, "sessions"), depth: 4 }];
835
+ return [{ path: join2(roots.codexRoot, "sessions"), depth: 4 }];
603
836
  case "cursor":
604
837
  return cursorDataPath ? [
605
- { path: join(cursorDataPath, "globalStorage", "state.vscdb") },
606
- { path: join(cursorDataPath, "workspaceStorage"), depth: 2 }
838
+ { path: join2(cursorDataPath, "globalStorage", "state.vscdb") },
839
+ { path: join2(cursorDataPath, "workspaceStorage"), depth: 2 }
607
840
  ] : [];
608
841
  case "kimi":
609
842
  return [
610
- { path: join(roots.kimiRoot, "sessions"), depth: 2 },
843
+ { path: join2(roots.kimiRoot, "sessions"), depth: 2 },
611
844
  { path: "data/kimi", depth: 2 }
612
845
  ];
613
846
  case "opencode":
614
847
  return [
615
- { path: join(roots.opencodeRoot, "opencode.db") },
848
+ { path: join2(roots.opencodeRoot, "opencode.db") },
616
849
  { path: "data/opencode/opencode.db" }
617
850
  ];
618
851
  default:
@@ -620,12 +853,14 @@ function resolveAgentWatchTargets(agentName) {
620
853
  }
621
854
  }
622
855
  var LiveScanStore = class {
623
- constructor(watchEnabled = true, scanOptions = {}) {
856
+ constructor(watchEnabled = true, scanOptions = {}, startupScanOptions = {}) {
624
857
  this.watchEnabled = watchEnabled;
625
858
  this.scanOptions = scanOptions;
859
+ this.startupScanOptions = startupScanOptions;
626
860
  }
627
861
  watchEnabled;
628
862
  scanOptions;
863
+ startupScanOptions;
629
864
  agents = [];
630
865
  byAgent = {};
631
866
  sessions = [];
@@ -636,33 +871,30 @@ var LiveScanStore = class {
636
871
  pendingRefreshes = /* @__PURE__ */ new Set();
637
872
  watchers = [];
638
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
+ });
639
882
  const initialResult = await scanSessions({
640
883
  ...this.scanOptions,
641
- useCache: true,
642
- 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
643
889
  });
644
- const knownAgents = createRegisteredAgents();
645
- const agentMap = /* @__PURE__ */ new Map();
646
- const allowedAgents = this.getAllowedAgents();
647
- for (const agent of initialResult.agents) {
648
- agentMap.set(agent.name, agent);
649
- }
650
- for (const agent of knownAgents) {
651
- if (!agentMap.has(agent.name)) {
652
- agentMap.set(agent.name, agent);
653
- }
654
- }
655
- this.agents = [...agentMap.values()].filter((agent) => {
656
- if (!allowedAgents) {
657
- return true;
658
- }
659
- 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
+ )
660
897
  });
661
- for (const agent of this.agents) {
662
- this.byAgent[agent.name] = sortSessions(initialResult.byAgent[agent.name] ?? []);
663
- this.refreshTimestamps.set(agent.name, Date.now());
664
- }
665
- this.rebuildSessions();
666
898
  if (this.watchEnabled) {
667
899
  this.startWatching();
668
900
  }
@@ -696,6 +928,34 @@ var LiveScanStore = class {
696
928
  rebuildSessions() {
697
929
  this.sessions = sortSessions(Object.values(this.byAgent).flat());
698
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
+ }
699
959
  getAllowedAgents() {
700
960
  if (!this.scanOptions.agents?.length) {
701
961
  return null;
@@ -703,7 +963,7 @@ var LiveScanStore = class {
703
963
  return new Set(this.scanOptions.agents.map((agent) => agent.toLowerCase()));
704
964
  }
705
965
  applyFilters(sessions) {
706
- return filterSessions(sessions, this.scanOptions);
966
+ return filterSessions(sessions, { ...this.scanOptions, ...this.startupScanOptions });
707
967
  }
708
968
  startWatching() {
709
969
  for (const agent of this.agents) {
@@ -715,8 +975,16 @@ var LiveScanStore = class {
715
975
  (target, index, items) => items.findIndex((item) => item.path === target.path && item.depth === target.depth) === index
716
976
  );
717
977
  if (watchTargets.length === 0) {
978
+ appLogger.debug("watch.skip", { agent: agent.name });
718
979
  continue;
719
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
+ });
720
988
  const watcher = chokidar.watch(
721
989
  watchTargets.map((target) => target.path),
722
990
  {
@@ -735,12 +1003,14 @@ var LiveScanStore = class {
735
1003
  this.scheduleRefresh(agent.name);
736
1004
  });
737
1005
  watcher.on("error", (error) => {
1006
+ appLogger.error("watch.error", { agent: agent.name, error });
738
1007
  console.error(`[${agent.name}] File watcher failed:`, error);
739
1008
  });
740
1009
  this.watchers.push(watcher);
741
1010
  }
742
1011
  }
743
1012
  scheduleRefresh(agentName, delayMs = 200) {
1013
+ appLogger.debug("scan.refresh.schedule", { agent: agentName, delay_ms: delayMs });
744
1014
  const existing = this.refreshTimers.get(agentName);
745
1015
  if (existing) {
746
1016
  clearTimeout(existing);
@@ -753,6 +1023,7 @@ var LiveScanStore = class {
753
1023
  }
754
1024
  async refreshAgent(agentName) {
755
1025
  if (this.refreshInFlight.has(agentName)) {
1026
+ appLogger.debug("scan.refresh.pending", { agent: agentName });
756
1027
  this.pendingRefreshes.add(agentName);
757
1028
  return;
758
1029
  }
@@ -767,8 +1038,10 @@ var LiveScanStore = class {
767
1038
  }
768
1039
  }
769
1040
  async runRefresh(agentName) {
1041
+ const startedAt = performance.now();
770
1042
  const agent = this.agents.find((item) => item.name === agentName);
771
1043
  if (!agent) {
1044
+ appLogger.warn("scan.refresh.missing_agent", { agent: agentName });
772
1045
  return;
773
1046
  }
774
1047
  const previousSessions = this.byAgent[agentName] ?? [];
@@ -782,17 +1055,23 @@ var LiveScanStore = class {
782
1055
  );
783
1056
  this.refreshTimestamps.set(agentName, checkResult.timestamp);
784
1057
  if (!checkResult.hasChanges) {
1058
+ appLogger.debug("scan.refresh.unchanged", {
1059
+ agent: agentName,
1060
+ duration_ms: Math.round(performance.now() - startedAt)
1061
+ });
785
1062
  return;
786
1063
  }
787
1064
  nextSessions = await Promise.resolve(
788
1065
  agent.incrementalScan(previousSessions, checkResult.changedIds ?? [])
789
1066
  );
790
1067
  } else {
791
- nextSessions = await Promise.resolve(agent.scan());
1068
+ nextSessions = await Promise.resolve(agent.scan(this.startupScanOptions));
792
1069
  this.refreshTimestamps.set(agentName, Date.now());
793
1070
  }
794
1071
  nextSessions = this.applyFilters(nextSessions);
795
- saveCachedSessions(agentName, nextSessions, buildAgentCacheMeta(agent));
1072
+ if (!this.hasStartupWindow()) {
1073
+ saveCachedSessions(agentName, nextSessions, buildAgentCacheMeta(agent));
1074
+ }
796
1075
  syncSessionSearchIndex(agentName, nextSessions, (sessionId) => agent.getSessionData(sessionId));
797
1076
  const event = buildUpdateEvent(agentName, previousSessions, nextSessions);
798
1077
  this.byAgent[agentName] = sortSessions(nextSessions);
@@ -801,6 +1080,14 @@ var LiveScanStore = class {
801
1080
  event.totalSessions = this.sessions.length;
802
1081
  this.emit(event);
803
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
+ });
804
1091
  }
805
1092
  };
806
1093
 
@@ -936,6 +1223,7 @@ var main = defineCommand({
936
1223
  }
937
1224
  },
938
1225
  async run({ args }) {
1226
+ const startedAt = performance.now();
939
1227
  const port = parseInt(args.port, 10) || 4321;
940
1228
  const noOpen = args.noOpen;
941
1229
  const jsonOnly = args.json;
@@ -945,11 +1233,22 @@ var main = defineCommand({
945
1233
  if (trace) {
946
1234
  perf.enable();
947
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
+ });
948
1245
  if (clearCache) {
949
- const { clearCache: clear } = await import("./dist-5NKHH33A.js");
1246
+ const { clearCache: clear } = await import("./dist-DMEDEJ2D.js");
950
1247
  clear();
1248
+ appLogger.info("cache.clear");
951
1249
  console.log("Cache cleared.");
952
1250
  }
1251
+ void refreshPricingCache();
953
1252
  let targetSession = null;
954
1253
  if (args.session) {
955
1254
  targetSession = parseSessionUri(args.session);
@@ -979,9 +1278,19 @@ var main = defineCommand({
979
1278
  cwd: cwdFilter,
980
1279
  useCache
981
1280
  };
982
- const store = new LiveScanStore(!jsonOnly, scanOptions);
1281
+ const startupScanOptions = targetSession || jsonOnly ? {} : { from: listDefaultFrom, to: listDefaultTo };
1282
+ const store = new LiveScanStore(!jsonOnly, scanOptions, startupScanOptions);
983
1283
  await store.initialize();
984
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
+ });
985
1294
  if (trace) {
986
1295
  console.log(perf.getReport());
987
1296
  }
@@ -1004,6 +1313,10 @@ var main = defineCommand({
1004
1313
  })),
1005
1314
  sessions: windowed
1006
1315
  };
1316
+ appLogger.info("cli.json_output", {
1317
+ sessions: windowed.length,
1318
+ duration_ms: Math.round(performance.now() - startedAt)
1319
+ });
1007
1320
  console.log(JSON.stringify(output, null, 2));
1008
1321
  return;
1009
1322
  }
@@ -1022,9 +1335,15 @@ var main = defineCommand({
1022
1335
  }
1023
1336
  console.log(` ${url}`);
1024
1337
  console.log("");
1338
+ appLogger.info("cli.ready", {
1339
+ url,
1340
+ duration_ms: Math.round(performance.now() - startedAt),
1341
+ log_path: appLogger.getLogPath()
1342
+ });
1025
1343
  if (!noOpen) {
1026
1344
  const open = (await import("open")).default;
1027
1345
  const targetUrl = targetSession ? `${url}/${targetSession.agent.toLowerCase()}/${targetSession.sessionId}` : url;
1346
+ appLogger.info("browser.open", { url: targetUrl });
1028
1347
  await open(targetUrl);
1029
1348
  }
1030
1349
  }