codesesh 0.1.4 → 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,53 +121,567 @@ 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
 
93
299
  // src/server.ts
94
300
  function findWebDistPath() {
95
- const __dirname = dirname(fileURLToPath(import.meta.url));
96
- const packagedPath = resolve(__dirname, "web");
301
+ const __dirname2 = dirname(fileURLToPath(import.meta.url));
302
+ const packagedPath = resolve(__dirname2, "web");
97
303
  if (existsSync(packagedPath)) {
98
304
  return packagedPath;
99
305
  }
100
- const devPath = resolve(__dirname, "../../../apps/web/dist");
306
+ const devPath = resolve(__dirname2, "../../../apps/web/dist");
101
307
  if (existsSync(devPath)) {
102
308
  return devPath;
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
+ }
372
+ };
373
+ }
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()
120
438
  };
121
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
+ };
122
667
 
123
668
  // src/output.ts
124
669
  import { consola } from "consola";
670
+
671
+ // src/version.ts
672
+ import { readFileSync } from "fs";
673
+ import { resolve as resolve2, dirname as dirname3 } from "path";
674
+ import { fileURLToPath as fileURLToPath2 } from "url";
675
+ var __dirname = dirname3(fileURLToPath2(import.meta.url));
676
+ var pkg = JSON.parse(readFileSync(resolve2(__dirname, "../package.json"), "utf-8"));
677
+ var VERSION = pkg.version;
678
+
679
+ // src/output.ts
125
680
  function printScanResults(agents, result) {
126
681
  consola.log("");
127
682
  consola.box({
128
683
  title: "CodeSesh",
129
- message: `v0.1.1 \u2022 ${result.sessions.length} sessions discovered`,
684
+ message: `v${VERSION} \u2022 ${result.sessions.length} sessions discovered`,
130
685
  style: {
131
686
  padding: 1,
132
687
  borderColor: "cyan"
@@ -161,7 +716,6 @@ function dim(text) {
161
716
  }
162
717
 
163
718
  // src/index.ts
164
- var VERSION = "0.1.1";
165
719
  function parseDateToTimestamp(dateStr) {
166
720
  const date = new Date(dateStr);
167
721
  if (Number.isNaN(date.getTime())) {
@@ -253,7 +807,7 @@ var main = defineCommand({
253
807
  perf.enable();
254
808
  }
255
809
  if (clearCache) {
256
- const { clearCache: clear } = await import("./dist-ZF35YB7B.js");
810
+ const { clearCache: clear } = await import("./dist-6EV6SS6N.js");
257
811
  clear();
258
812
  console.log("Cache cleared.");
259
813
  }
@@ -269,27 +823,35 @@ var main = defineCommand({
269
823
  if (cwdFilter === ".") {
270
824
  cwdFilter = process.cwd();
271
825
  }
272
- let fromTimestamp;
826
+ let listDefaultFrom;
827
+ let listDefaultDays;
273
828
  if (args.from) {
274
- fromTimestamp = parseDateToTimestamp(args.from);
829
+ listDefaultFrom = parseDateToTimestamp(args.from);
275
830
  } else {
276
831
  const days = parseInt(args.days, 10);
277
832
  if (!Number.isNaN(days) && days > 0) {
278
- fromTimestamp = Date.now() - days * 24 * 60 * 60 * 1e3;
833
+ listDefaultFrom = Date.now() - days * 24 * 60 * 60 * 1e3;
834
+ listDefaultDays = days;
279
835
  }
280
836
  }
837
+ const listDefaultTo = args.to ? parseDateToTimestamp(args.to) : void 0;
281
838
  const scanOptions = {
282
839
  agents: targetSession ? [targetSession.agent] : args.agent ? args.agent.split(",").map((a) => a.trim()) : void 0,
283
840
  cwd: cwdFilter,
284
- from: fromTimestamp,
285
- to: args.to ? parseDateToTimestamp(args.to) : void 0,
286
841
  useCache
287
842
  };
288
- const result = await scanSessionsAsync(scanOptions);
843
+ const store = new LiveScanStore(!jsonOnly, scanOptions);
844
+ await store.initialize();
845
+ const result = store.getSnapshot();
289
846
  if (trace) {
290
847
  console.log(perf.getReport());
291
848
  }
292
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
+ });
293
855
  const info = getAgentInfoMap(
294
856
  Object.fromEntries(Object.entries(result.byAgent).map(([k, v]) => [k, v.length]))
295
857
  );
@@ -300,15 +862,25 @@ var main = defineCommand({
300
862
  count,
301
863
  available: count > 0
302
864
  })),
303
- sessions: result.sessions
865
+ sessions: windowed
304
866
  };
305
867
  console.log(JSON.stringify(output, null, 2));
306
868
  return;
307
869
  }
308
870
  const agents = createRegisteredAgents();
309
871
  printScanResults(agents, result);
310
- const { url } = await createServer(port, result);
311
- 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}`);
312
884
  console.log("");
313
885
  if (!noOpen) {
314
886
  const open = (await import("open")).default;