codesesh 0.1.5 → 0.2.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,10 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  createRegisteredAgents,
4
+ filterSessions,
4
5
  getAgentInfoMap,
6
+ getCursorDataPath,
5
7
  perf,
6
- scanSessionsAsync
7
- } from "./chunk-FG2FZIU5.js";
8
+ resolveProviderRoots,
9
+ saveCachedSessions,
10
+ scanSessions
11
+ } from "./chunk-2UNXB2D3.js";
8
12
 
9
13
  // src/index.ts
10
14
  import { defineCommand, runMain } from "citty";
@@ -22,20 +26,62 @@ import { fileURLToPath } from "url";
22
26
  import { Hono } from "hono";
23
27
 
24
28
  // src/api/handlers.ts
25
- function handleGetAgents(c, scanResult) {
26
- const counts = {};
27
- for (const agent of scanResult.agents) {
28
- counts[agent.name] = scanResult.byAgent[agent.name]?.length ?? 0;
29
- }
29
+ function getTotalTokens(stats) {
30
+ return stats.total_tokens ?? stats.total_input_tokens + stats.total_output_tokens;
31
+ }
32
+ function getSessionActivityTime(session) {
33
+ return session.time_updated ?? session.time_created;
34
+ }
35
+ function parseDateParam(value, fallback) {
36
+ if (value == null) return fallback;
37
+ const ts = new Date(value).getTime();
38
+ return Number.isNaN(ts) ? fallback : ts;
39
+ }
40
+ function filterSessionsByWindow(sessions, from, to) {
41
+ if (from == null && to == null) return sessions;
42
+ return sessions.filter((s) => {
43
+ if (from != null && s.time_created < from) return false;
44
+ if (to != null && s.time_created > to) return false;
45
+ return true;
46
+ });
47
+ }
48
+ function filterSessionsByActivityWindow(sessions, from, to) {
49
+ if (from == null && to == null) return sessions;
50
+ return sessions.filter((session) => {
51
+ const activity = getSessionActivityTime(session);
52
+ if (from != null && activity < from) return false;
53
+ if (to != null && activity > to) return false;
54
+ return true;
55
+ });
56
+ }
57
+ function handleGetConfig(c, defaults) {
58
+ return c.json({
59
+ window: {
60
+ from: defaults.from,
61
+ to: defaults.to,
62
+ days: defaults.days
63
+ }
64
+ });
65
+ }
66
+ function handleGetAgents(c, scanSource, defaults = {}) {
67
+ const scanResult = scanSource.getSnapshot();
68
+ const { from, to } = defaults;
69
+ const counts = Object.fromEntries(
70
+ Object.entries(scanResult.byAgent).map(([agentName, sessions]) => [
71
+ agentName,
72
+ filterSessionsByWindow(sessions, from, to).length
73
+ ])
74
+ );
30
75
  const info = getAgentInfoMap(counts);
31
76
  return c.json(info);
32
77
  }
33
- function handleGetSessions(c, scanResult) {
78
+ function handleGetSessions(c, scanSource, defaults = {}) {
79
+ const scanResult = scanSource.getSnapshot();
34
80
  const agent = c.req.query("agent");
35
81
  const q = c.req.query("q")?.toLowerCase();
36
82
  const cwd = c.req.query("cwd")?.toLowerCase();
37
- const from = c.req.query("from");
38
- const to = c.req.query("to");
83
+ const from = parseDateParam(c.req.query("from"), defaults.from);
84
+ const to = parseDateParam(c.req.query("to"), defaults.to);
39
85
  let sessions = [];
40
86
  if (agent && scanResult.byAgent[agent]) {
41
87
  sessions = [...scanResult.byAgent[agent]];
@@ -45,24 +91,19 @@ function handleGetSessions(c, scanResult) {
45
91
  if (cwd) {
46
92
  sessions = sessions.filter((s) => s.directory.toLowerCase().includes(cwd));
47
93
  }
48
- if (from) {
49
- const fromTs = new Date(from).getTime();
50
- if (!Number.isNaN(fromTs)) {
51
- sessions = sessions.filter((s) => s.time_created >= fromTs);
52
- }
94
+ if (from != null) {
95
+ sessions = sessions.filter((s) => s.time_created >= from);
53
96
  }
54
- if (to) {
55
- const toTs = new Date(to).getTime();
56
- if (!Number.isNaN(toTs)) {
57
- sessions = sessions.filter((s) => s.time_created <= toTs);
58
- }
97
+ if (to != null) {
98
+ sessions = sessions.filter((s) => s.time_created <= to);
59
99
  }
60
100
  if (q) {
61
101
  sessions = sessions.filter((s) => s.title.toLowerCase().includes(q));
62
102
  }
63
103
  return c.json({ sessions });
64
104
  }
65
- async function handleGetSessionData(c, scanResult) {
105
+ async function handleGetSessionData(c, scanSource) {
106
+ const scanResult = scanSource.getSnapshot();
66
107
  const agentName = c.req.param("agent");
67
108
  const sessionId = c.req.param("id");
68
109
  if (!sessionId) {
@@ -80,13 +121,178 @@ async function handleGetSessionData(c, scanResult) {
80
121
  return c.json({ error: message }, 500);
81
122
  }
82
123
  }
124
+ function toLocalDateKey(ts) {
125
+ const d = new Date(ts);
126
+ const year = d.getFullYear();
127
+ const month = `${d.getMonth() + 1}`.padStart(2, "0");
128
+ const day = `${d.getDate()}`.padStart(2, "0");
129
+ return `${year}-${month}-${day}`;
130
+ }
131
+ function startOfLocalDay(ts) {
132
+ const d = new Date(ts);
133
+ d.setHours(0, 0, 0, 0);
134
+ return d.getTime();
135
+ }
136
+ function resolveDashboardWindow(defaults, queryDays, queryFrom, queryTo) {
137
+ const now = Date.now();
138
+ const todayStart = startOfLocalDay(now);
139
+ const toTs = parseDateParam(queryTo, defaults.to) ?? todayStart + 24 * 60 * 60 * 1e3 - 1;
140
+ const parsedDays = queryDays ? parseInt(queryDays, 10) : NaN;
141
+ let days = Number.isFinite(parsedDays) && parsedDays > 0 ? parsedDays : defaults.days;
142
+ const fromFromQuery = parseDateParam(queryFrom, void 0);
143
+ let fromTs;
144
+ if (fromFromQuery != null) {
145
+ fromTs = startOfLocalDay(fromFromQuery);
146
+ days ??= Math.max(1, Math.ceil((todayStart - fromTs) / 864e5) + 1);
147
+ } else if (days && days > 0) {
148
+ fromTs = todayStart - (days - 1) * 864e5;
149
+ } else if (defaults.from != null) {
150
+ fromTs = startOfLocalDay(defaults.from);
151
+ days = Math.max(1, Math.ceil((todayStart - fromTs) / 864e5) + 1);
152
+ } else {
153
+ days = 30;
154
+ fromTs = todayStart - (days - 1) * 864e5;
155
+ }
156
+ return { from: fromTs, to: toTs, days };
157
+ }
158
+ function handleGetDashboard(c, scanSource, defaults = {}) {
159
+ const scanResult = scanSource.getSnapshot();
160
+ const { from, to, days } = resolveDashboardWindow(
161
+ defaults,
162
+ c.req.query("days"),
163
+ c.req.query("from"),
164
+ c.req.query("to")
165
+ );
166
+ const windowed = filterSessionsByActivityWindow(scanResult.sessions, from, to);
167
+ const agentInfo = getAgentInfoMap(
168
+ Object.fromEntries(
169
+ Object.entries(scanResult.byAgent).map(([name, sessions]) => [
170
+ name,
171
+ filterSessionsByActivityWindow(sessions, from, to).length
172
+ ])
173
+ )
174
+ );
175
+ const agentInfoMap = new Map(agentInfo.map((a) => [a.name, a]));
176
+ let totalMessages = 0;
177
+ let totalTokens = 0;
178
+ let totalCost = 0;
179
+ let latestActivity = 0;
180
+ for (const session of windowed) {
181
+ totalMessages += session.stats.message_count;
182
+ totalTokens += getTotalTokens(session.stats);
183
+ totalCost += session.stats.total_cost ?? 0;
184
+ const activity = getSessionActivityTime(session);
185
+ if (activity > latestActivity) latestActivity = activity;
186
+ }
187
+ const perAgent = Object.entries(scanResult.byAgent).map(([name, sessions]) => {
188
+ const info = agentInfoMap.get(name);
189
+ const agentWindowed = filterSessionsByActivityWindow(sessions, from, to);
190
+ let messages = 0;
191
+ let tokens = 0;
192
+ for (const s of agentWindowed) {
193
+ messages += s.stats.message_count;
194
+ tokens += getTotalTokens(s.stats);
195
+ }
196
+ return {
197
+ name,
198
+ displayName: info?.displayName ?? name,
199
+ icon: info?.icon ?? "",
200
+ sessions: agentWindowed.length,
201
+ messages,
202
+ tokens
203
+ };
204
+ }).filter((item) => item.sessions > 0).sort((a, b) => b.sessions - a.sessions);
205
+ const dailyMap = /* @__PURE__ */ new Map();
206
+ const bucketStart = startOfLocalDay(from);
207
+ for (let i = 0; i < days; i += 1) {
208
+ const ts = bucketStart + i * 864e5;
209
+ const key = toLocalDateKey(ts);
210
+ dailyMap.set(key, { date: key, sessions: 0, messages: 0 });
211
+ }
212
+ for (const session of windowed) {
213
+ const key = toLocalDateKey(getSessionActivityTime(session));
214
+ const bucket = dailyMap.get(key);
215
+ if (bucket) {
216
+ bucket.sessions += 1;
217
+ bucket.messages += session.stats.message_count;
218
+ }
219
+ }
220
+ const dailyActivity = [...dailyMap.values()];
221
+ const recentSessions = [...windowed].sort((a, b) => getSessionActivityTime(b) - getSessionActivityTime(a)).slice(0, 10).map((session) => {
222
+ const agentKey = session.slug.split("/")[0] ?? "unknown";
223
+ return { ...session, agentName: agentKey };
224
+ });
225
+ const data = {
226
+ totals: {
227
+ sessions: windowed.length,
228
+ messages: totalMessages,
229
+ tokens: totalTokens,
230
+ cost: totalCost,
231
+ latestActivity: latestActivity || void 0
232
+ },
233
+ perAgent,
234
+ dailyActivity,
235
+ recentSessions,
236
+ window: { from, to, days }
237
+ };
238
+ return c.json(data);
239
+ }
83
240
 
84
241
  // src/api/routes.ts
85
- function createApiRoutes(scanResult) {
242
+ function createSseResponse(store, signal) {
243
+ const encoder = new TextEncoder();
244
+ return new Response(
245
+ new ReadableStream({
246
+ start(controller) {
247
+ const write = (event, data) => {
248
+ controller.enqueue(encoder.encode(`event: ${event}
249
+ `));
250
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}
251
+
252
+ `));
253
+ };
254
+ write("connected", { timestamp: Date.now() });
255
+ const unsubscribe = store.subscribe((event) => {
256
+ write(event.type, event);
257
+ });
258
+ const heartbeat = setInterval(() => {
259
+ controller.enqueue(encoder.encode(": keepalive\n\n"));
260
+ }, 15e3);
261
+ const close = () => {
262
+ clearInterval(heartbeat);
263
+ unsubscribe();
264
+ controller.close();
265
+ };
266
+ signal.addEventListener("abort", close, { once: true });
267
+ },
268
+ cancel() {
269
+ return;
270
+ }
271
+ }),
272
+ {
273
+ headers: {
274
+ "Cache-Control": "no-cache",
275
+ Connection: "keep-alive",
276
+ "Content-Type": "text/event-stream"
277
+ }
278
+ }
279
+ );
280
+ }
281
+ function createApiRoutes(scanSource, store, options = {}) {
86
282
  const api = new Hono();
87
- api.get("/agents", (c) => handleGetAgents(c, scanResult));
88
- api.get("/sessions", (c) => handleGetSessions(c, scanResult));
89
- api.get("/sessions/:agent/:id", (c) => handleGetSessionData(c, scanResult));
283
+ const listDefaults = {
284
+ from: options.defaultSessionFrom,
285
+ to: options.defaultSessionTo,
286
+ days: options.defaultSessionDays
287
+ };
288
+ api.get("/config", (c) => handleGetConfig(c, listDefaults));
289
+ api.get("/agents", (c) => handleGetAgents(c, scanSource, listDefaults));
290
+ api.get("/sessions", (c) => handleGetSessions(c, scanSource, listDefaults));
291
+ api.get("/sessions/:agent/:id", (c) => handleGetSessionData(c, scanSource));
292
+ api.get("/dashboard", (c) => handleGetDashboard(c, scanSource, listDefaults));
293
+ if (store) {
294
+ api.get("/events", (c) => createSseResponse(store, c.req.raw.signal));
295
+ }
90
296
  return api;
91
297
  }
92
298
 
@@ -103,31 +309,370 @@ function findWebDistPath() {
103
309
  }
104
310
  return null;
105
311
  }
106
- async function createServer(port, scanResult) {
312
+ function waitForListening(server) {
313
+ return new Promise((resolve3, reject) => {
314
+ const handleListening = () => {
315
+ server.off("error", handleError);
316
+ resolve3();
317
+ };
318
+ const handleError = (error) => {
319
+ server.off("listening", handleListening);
320
+ reject(error);
321
+ };
322
+ server.once("listening", handleListening);
323
+ server.once("error", handleError);
324
+ });
325
+ }
326
+ function getServerStartupErrorMessage(error, port) {
327
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "EADDRINUSE") {
328
+ return `Port ${port} \u5DF2\u88AB\u5360\u7528\uFF0C\u8BF7\u5173\u95ED\u73B0\u6709 CodeSesh \u8FDB\u7A0B\u6216\u6539\u7528 --port \u6307\u5B9A\u5176\u4ED6\u7AEF\u53E3\u3002`;
329
+ }
330
+ return error instanceof Error ? error.message : `\u542F\u52A8\u670D\u52A1\u5668\u5931\u8D25: ${String(error)}`;
331
+ }
332
+ async function createServer(port, store, options = {}) {
107
333
  const app = new Hono2();
108
334
  app.use("*", logger());
109
- app.route("/api", createApiRoutes(scanResult));
335
+ const routeOptions = {
336
+ defaultSessionFrom: options.defaultSessionFrom,
337
+ defaultSessionTo: options.defaultSessionTo,
338
+ defaultSessionDays: options.defaultSessionDays
339
+ };
340
+ app.route(
341
+ "/api",
342
+ createApiRoutes(
343
+ store,
344
+ "subscribe" in store ? store : void 0,
345
+ routeOptions
346
+ )
347
+ );
110
348
  const webDistPath = findWebDistPath();
111
349
  if (webDistPath) {
112
350
  app.use("/*", serveStatic({ root: webDistPath }));
113
351
  app.get("/*", serveStatic({ root: webDistPath, path: "index.html" }));
114
352
  }
115
353
  const server = serve({ fetch: app.fetch, port });
354
+ try {
355
+ await waitForListening(server);
356
+ } catch (error) {
357
+ server.close();
358
+ if (store.shutdown) {
359
+ await store.shutdown();
360
+ }
361
+ throw new Error(getServerStartupErrorMessage(error, port));
362
+ }
116
363
  const url = `http://localhost:${port}`;
117
364
  return {
118
365
  url,
119
- shutdown: () => server.close()
366
+ shutdown: () => {
367
+ server.close();
368
+ if (store.shutdown) {
369
+ void store.shutdown();
370
+ }
371
+ }
120
372
  };
121
373
  }
122
374
 
375
+ // src/live-scan.ts
376
+ import { existsSync as existsSync2 } from "fs";
377
+ import { dirname as dirname2, isAbsolute, join } from "path";
378
+ import chokidar from "chokidar";
379
+ function sortSessions(sessions) {
380
+ return [...sessions].sort(
381
+ (a, b) => (b.time_updated ?? b.time_created) - (a.time_updated ?? a.time_created)
382
+ );
383
+ }
384
+ function sessionSignature(session) {
385
+ return JSON.stringify([
386
+ session.title,
387
+ session.directory,
388
+ session.time_created,
389
+ session.time_updated ?? session.time_created,
390
+ session.stats.message_count,
391
+ session.stats.total_input_tokens,
392
+ session.stats.total_output_tokens,
393
+ session.stats.total_cost,
394
+ session.stats.total_tokens ?? 0
395
+ ]);
396
+ }
397
+ function buildAgentCacheMeta(agent) {
398
+ const metaMap = agent.getSessionMetaMap?.();
399
+ const meta = {};
400
+ if (!metaMap) return meta;
401
+ for (const [id, data] of metaMap.entries()) {
402
+ meta[id] = { id, ...data };
403
+ }
404
+ return meta;
405
+ }
406
+ function buildUpdateEvent(agentName, previousSessions, nextSessions) {
407
+ const previousMap = new Map(previousSessions.map((session) => [session.id, session]));
408
+ const nextMap = new Map(nextSessions.map((session) => [session.id, session]));
409
+ let newSessions = 0;
410
+ let updatedSessions = 0;
411
+ let removedSessions = 0;
412
+ for (const [id, session] of nextMap.entries()) {
413
+ const previous = previousMap.get(id);
414
+ if (!previous) {
415
+ newSessions += 1;
416
+ continue;
417
+ }
418
+ if (sessionSignature(previous) !== sessionSignature(session)) {
419
+ updatedSessions += 1;
420
+ }
421
+ }
422
+ for (const id of previousMap.keys()) {
423
+ if (!nextMap.has(id)) {
424
+ removedSessions += 1;
425
+ }
426
+ }
427
+ if (newSessions === 0 && updatedSessions === 0 && removedSessions === 0) {
428
+ return null;
429
+ }
430
+ return {
431
+ type: "sessions-updated",
432
+ changedAgents: [agentName],
433
+ newSessions,
434
+ updatedSessions,
435
+ removedSessions,
436
+ totalSessions: nextSessions.length,
437
+ timestamp: Date.now()
438
+ };
439
+ }
440
+ function closestWatchablePath(targetPath) {
441
+ if (!isAbsolute(targetPath) && !existsSync2(targetPath)) {
442
+ return null;
443
+ }
444
+ let current = targetPath;
445
+ while (!existsSync2(current)) {
446
+ const parent = dirname2(current);
447
+ if (parent === current) {
448
+ return null;
449
+ }
450
+ current = parent;
451
+ }
452
+ return current;
453
+ }
454
+ function resolveAgentWatchTargets(agentName) {
455
+ const roots = resolveProviderRoots();
456
+ const cursorDataPath = getCursorDataPath();
457
+ switch (agentName) {
458
+ case "claudecode":
459
+ return [
460
+ { path: join(roots.claudeRoot, "projects"), depth: 2 },
461
+ { path: "data/claudecode", depth: 2 }
462
+ ];
463
+ case "codex":
464
+ return [{ path: join(roots.codexRoot, "sessions"), depth: 4 }];
465
+ case "cursor":
466
+ return cursorDataPath ? [
467
+ { path: join(cursorDataPath, "globalStorage", "state.vscdb") },
468
+ { path: join(cursorDataPath, "workspaceStorage"), depth: 2 }
469
+ ] : [];
470
+ case "kimi":
471
+ return [
472
+ { path: join(roots.kimiRoot, "sessions"), depth: 2 },
473
+ { path: "data/kimi", depth: 2 }
474
+ ];
475
+ case "opencode":
476
+ return [
477
+ { path: join(roots.opencodeRoot, "opencode.db") },
478
+ { path: "data/opencode/opencode.db" }
479
+ ];
480
+ default:
481
+ return [];
482
+ }
483
+ }
484
+ var LiveScanStore = class {
485
+ constructor(watchEnabled = true, scanOptions = {}) {
486
+ this.watchEnabled = watchEnabled;
487
+ this.scanOptions = scanOptions;
488
+ }
489
+ watchEnabled;
490
+ scanOptions;
491
+ agents = [];
492
+ byAgent = {};
493
+ sessions = [];
494
+ listeners = /* @__PURE__ */ new Set();
495
+ refreshTimers = /* @__PURE__ */ new Map();
496
+ refreshTimestamps = /* @__PURE__ */ new Map();
497
+ refreshInFlight = /* @__PURE__ */ new Set();
498
+ pendingRefreshes = /* @__PURE__ */ new Set();
499
+ watchers = [];
500
+ async initialize() {
501
+ const initialResult = await scanSessions({
502
+ ...this.scanOptions,
503
+ useCache: true,
504
+ smartRefresh: false
505
+ });
506
+ const knownAgents = createRegisteredAgents();
507
+ const agentMap = /* @__PURE__ */ new Map();
508
+ const allowedAgents = this.getAllowedAgents();
509
+ for (const agent of initialResult.agents) {
510
+ agentMap.set(agent.name, agent);
511
+ }
512
+ for (const agent of knownAgents) {
513
+ if (!agentMap.has(agent.name)) {
514
+ agentMap.set(agent.name, agent);
515
+ }
516
+ }
517
+ this.agents = [...agentMap.values()].filter((agent) => {
518
+ if (!allowedAgents) {
519
+ return true;
520
+ }
521
+ return allowedAgents.has(agent.name.toLowerCase());
522
+ });
523
+ for (const agent of this.agents) {
524
+ this.byAgent[agent.name] = sortSessions(initialResult.byAgent[agent.name] ?? []);
525
+ this.refreshTimestamps.set(agent.name, Date.now());
526
+ }
527
+ this.rebuildSessions();
528
+ if (this.watchEnabled) {
529
+ this.startWatching();
530
+ }
531
+ }
532
+ getSnapshot() {
533
+ return {
534
+ sessions: this.sessions,
535
+ byAgent: this.byAgent,
536
+ agents: this.agents
537
+ };
538
+ }
539
+ subscribe(listener) {
540
+ this.listeners.add(listener);
541
+ return () => {
542
+ this.listeners.delete(listener);
543
+ };
544
+ }
545
+ async shutdown() {
546
+ for (const timer of this.refreshTimers.values()) {
547
+ clearTimeout(timer);
548
+ }
549
+ this.refreshTimers.clear();
550
+ await Promise.all(this.watchers.map((watcher) => watcher.close()));
551
+ this.watchers = [];
552
+ }
553
+ emit(event) {
554
+ for (const listener of this.listeners) {
555
+ listener(event);
556
+ }
557
+ }
558
+ rebuildSessions() {
559
+ this.sessions = sortSessions(Object.values(this.byAgent).flat());
560
+ }
561
+ getAllowedAgents() {
562
+ if (!this.scanOptions.agents?.length) {
563
+ return null;
564
+ }
565
+ return new Set(this.scanOptions.agents.map((agent) => agent.toLowerCase()));
566
+ }
567
+ applyFilters(sessions) {
568
+ return filterSessions(sessions, this.scanOptions);
569
+ }
570
+ startWatching() {
571
+ for (const agent of this.agents) {
572
+ const rawTargets = resolveAgentWatchTargets(agent.name);
573
+ const watchTargets = rawTargets.map((target) => {
574
+ const watchPath = closestWatchablePath(target.path);
575
+ return watchPath ? { ...target, path: watchPath } : null;
576
+ }).filter((target) => target !== null).filter(
577
+ (target, index, items) => items.findIndex((item) => item.path === target.path && item.depth === target.depth) === index
578
+ );
579
+ if (watchTargets.length === 0) {
580
+ continue;
581
+ }
582
+ const watcher = chokidar.watch(
583
+ watchTargets.map((target) => target.path),
584
+ {
585
+ ignoreInitial: true,
586
+ awaitWriteFinish: {
587
+ stabilityThreshold: 250,
588
+ pollInterval: 100
589
+ },
590
+ depth: watchTargets.reduce(
591
+ (maxDepth, target) => Math.max(maxDepth, target.depth ?? 0),
592
+ 0
593
+ )
594
+ }
595
+ );
596
+ watcher.on("all", () => {
597
+ this.scheduleRefresh(agent.name);
598
+ });
599
+ watcher.on("error", (error) => {
600
+ console.error(`[${agent.name}] File watcher failed:`, error);
601
+ });
602
+ this.watchers.push(watcher);
603
+ }
604
+ }
605
+ scheduleRefresh(agentName, delayMs = 200) {
606
+ const existing = this.refreshTimers.get(agentName);
607
+ if (existing) {
608
+ clearTimeout(existing);
609
+ }
610
+ const timer = setTimeout(() => {
611
+ this.refreshTimers.delete(agentName);
612
+ void this.refreshAgent(agentName);
613
+ }, delayMs);
614
+ this.refreshTimers.set(agentName, timer);
615
+ }
616
+ async refreshAgent(agentName) {
617
+ if (this.refreshInFlight.has(agentName)) {
618
+ this.pendingRefreshes.add(agentName);
619
+ return;
620
+ }
621
+ this.refreshInFlight.add(agentName);
622
+ try {
623
+ await this.runRefresh(agentName);
624
+ } finally {
625
+ this.refreshInFlight.delete(agentName);
626
+ if (this.pendingRefreshes.delete(agentName)) {
627
+ this.scheduleRefresh(agentName, 100);
628
+ }
629
+ }
630
+ }
631
+ async runRefresh(agentName) {
632
+ const agent = this.agents.find((item) => item.name === agentName);
633
+ if (!agent) {
634
+ return;
635
+ }
636
+ const previousSessions = this.byAgent[agentName] ?? [];
637
+ let nextSessions = previousSessions;
638
+ if (!agent.isAvailable()) {
639
+ nextSessions = [];
640
+ this.refreshTimestamps.set(agentName, Date.now());
641
+ } else if (previousSessions.length > 0 && agent.checkForChanges && agent.incrementalScan) {
642
+ const checkResult = await Promise.resolve(
643
+ agent.checkForChanges(this.refreshTimestamps.get(agentName) ?? 0, previousSessions)
644
+ );
645
+ this.refreshTimestamps.set(agentName, checkResult.timestamp);
646
+ if (!checkResult.hasChanges) {
647
+ return;
648
+ }
649
+ nextSessions = await Promise.resolve(
650
+ agent.incrementalScan(previousSessions, checkResult.changedIds ?? [])
651
+ );
652
+ } else {
653
+ nextSessions = await Promise.resolve(agent.scan());
654
+ this.refreshTimestamps.set(agentName, Date.now());
655
+ }
656
+ nextSessions = this.applyFilters(nextSessions);
657
+ saveCachedSessions(agentName, nextSessions, buildAgentCacheMeta(agent));
658
+ const event = buildUpdateEvent(agentName, previousSessions, nextSessions);
659
+ this.byAgent[agentName] = sortSessions(nextSessions);
660
+ this.rebuildSessions();
661
+ if (event) {
662
+ event.totalSessions = this.sessions.length;
663
+ this.emit(event);
664
+ }
665
+ }
666
+ };
667
+
123
668
  // src/output.ts
124
669
  import { consola } from "consola";
125
670
 
126
671
  // src/version.ts
127
672
  import { readFileSync } from "fs";
128
- import { resolve as resolve2, dirname as dirname2 } from "path";
673
+ import { resolve as resolve2, dirname as dirname3 } from "path";
129
674
  import { fileURLToPath as fileURLToPath2 } from "url";
130
- var __dirname = dirname2(fileURLToPath2(import.meta.url));
675
+ var __dirname = dirname3(fileURLToPath2(import.meta.url));
131
676
  var pkg = JSON.parse(readFileSync(resolve2(__dirname, "../package.json"), "utf-8"));
132
677
  var VERSION = pkg.version;
133
678
 
@@ -262,7 +807,7 @@ var main = defineCommand({
262
807
  perf.enable();
263
808
  }
264
809
  if (clearCache) {
265
- const { clearCache: clear } = await import("./dist-ZF35YB7B.js");
810
+ const { clearCache: clear } = await import("./dist-6EV6SS6N.js");
266
811
  clear();
267
812
  console.log("Cache cleared.");
268
813
  }
@@ -278,27 +823,35 @@ var main = defineCommand({
278
823
  if (cwdFilter === ".") {
279
824
  cwdFilter = process.cwd();
280
825
  }
281
- let fromTimestamp;
826
+ let listDefaultFrom;
827
+ let listDefaultDays;
282
828
  if (args.from) {
283
- fromTimestamp = parseDateToTimestamp(args.from);
829
+ listDefaultFrom = parseDateToTimestamp(args.from);
284
830
  } else {
285
831
  const days = parseInt(args.days, 10);
286
832
  if (!Number.isNaN(days) && days > 0) {
287
- fromTimestamp = Date.now() - days * 24 * 60 * 60 * 1e3;
833
+ listDefaultFrom = Date.now() - days * 24 * 60 * 60 * 1e3;
834
+ listDefaultDays = days;
288
835
  }
289
836
  }
837
+ const listDefaultTo = args.to ? parseDateToTimestamp(args.to) : void 0;
290
838
  const scanOptions = {
291
839
  agents: targetSession ? [targetSession.agent] : args.agent ? args.agent.split(",").map((a) => a.trim()) : void 0,
292
840
  cwd: cwdFilter,
293
- from: fromTimestamp,
294
- to: args.to ? parseDateToTimestamp(args.to) : void 0,
295
841
  useCache
296
842
  };
297
- const result = await scanSessionsAsync(scanOptions);
843
+ const store = new LiveScanStore(!jsonOnly, scanOptions);
844
+ await store.initialize();
845
+ const result = store.getSnapshot();
298
846
  if (trace) {
299
847
  console.log(perf.getReport());
300
848
  }
301
849
  if (jsonOnly) {
850
+ const windowed = result.sessions.filter((s) => {
851
+ if (listDefaultFrom != null && s.time_created < listDefaultFrom) return false;
852
+ if (listDefaultTo != null && s.time_created > listDefaultTo) return false;
853
+ return true;
854
+ });
302
855
  const info = getAgentInfoMap(
303
856
  Object.fromEntries(Object.entries(result.byAgent).map(([k, v]) => [k, v.length]))
304
857
  );
@@ -309,15 +862,25 @@ var main = defineCommand({
309
862
  count,
310
863
  available: count > 0
311
864
  })),
312
- sessions: result.sessions
865
+ sessions: windowed
313
866
  };
314
867
  console.log(JSON.stringify(output, null, 2));
315
868
  return;
316
869
  }
317
870
  const agents = createRegisteredAgents();
318
871
  printScanResults(agents, result);
319
- const { url } = await createServer(port, result);
320
- console.log(` http://localhost:${port}`);
872
+ let url;
873
+ try {
874
+ ({ url } = await createServer(port, store, {
875
+ defaultSessionFrom: listDefaultFrom,
876
+ defaultSessionTo: listDefaultTo,
877
+ defaultSessionDays: listDefaultDays
878
+ }));
879
+ } catch (error) {
880
+ console.error(getServerStartupErrorMessage(error, port));
881
+ process.exit(1);
882
+ }
883
+ console.log(` ${url}`);
321
884
  console.log("");
322
885
  if (!noOpen) {
323
886
  const open = (await import("open")).default;