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