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/README.md +5 -3
- package/dist/{chunk-BGQXQTWM.js → chunk-FZNZAMTZ.js} +1321 -173
- package/dist/chunk-FZNZAMTZ.js.map +1 -0
- package/dist/{dist-5NKHH33A.js → dist-DMEDEJ2D.js} +38 -4
- package/dist/index.js +378 -59
- package/dist/index.js.map +1 -1
- package/dist/web/assets/index-BRW_TBMw.js +106 -0
- package/dist/web/assets/index-CCgk7cPa.css +2 -0
- package/dist/web/assets/markdown-CnUlvKkZ.js +14 -0
- package/dist/web/assets/react-DT3QPCDf.js +1821 -0
- package/dist/web/assets/rolldown-runtime-Dw2cE7zH.js +1 -0
- package/dist/web/assets/syntax-DcanuzfQ.js +6 -0
- package/dist/web/assets/vendor-CWmLg_mG.js +43 -0
- package/dist/web/index.html +7 -2
- package/package.json +2 -3
- package/dist/chunk-BGQXQTWM.js.map +0 -1
- package/dist/web/assets/index-B78jpjIp.js +0 -68
- package/dist/web/assets/index-gSYgPU_H.css +0 -2
- /package/dist/{dist-5NKHH33A.js.map → dist-DMEDEJ2D.js.map} +0 -0
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-
|
|
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 {
|
|
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")
|
|
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 (
|
|
117
|
-
sessions = sessions.filter((s) => s.
|
|
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
|
-
|
|
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
|
|
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 =
|
|
251
|
-
days ??= Math.max(1, Math.ceil((
|
|
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 =
|
|
256
|
-
days
|
|
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 =
|
|
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
|
-
|
|
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 (
|
|
652
|
+
if (existsSync2(packagedPath)) {
|
|
442
653
|
return packagedPath;
|
|
443
654
|
}
|
|
444
655
|
const devPath = resolve(__dirname2, "../../../apps/web/dist");
|
|
445
|
-
if (
|
|
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("*",
|
|
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
|
|
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) && !
|
|
812
|
+
if (!isAbsolute(targetPath) && !existsSync3(targetPath)) {
|
|
580
813
|
return null;
|
|
581
814
|
}
|
|
582
815
|
let current = targetPath;
|
|
583
|
-
while (!
|
|
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:
|
|
831
|
+
{ path: join2(roots.claudeRoot, "projects"), depth: 2 },
|
|
599
832
|
{ path: "data/claudecode", depth: 2 }
|
|
600
833
|
];
|
|
601
834
|
case "codex":
|
|
602
|
-
return [{ path:
|
|
835
|
+
return [{ path: join2(roots.codexRoot, "sessions"), depth: 4 }];
|
|
603
836
|
case "cursor":
|
|
604
837
|
return cursorDataPath ? [
|
|
605
|
-
{ path:
|
|
606
|
-
{ path:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
642
|
-
|
|
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
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
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
|
-
|
|
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-
|
|
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
|
|
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
|
}
|