codesesh 0.4.0 → 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-EIIG7J6V.js → chunk-FZNZAMTZ.js} +1329 -174
- package/dist/chunk-FZNZAMTZ.js.map +1 -0
- package/dist/{dist-2YXXOCZJ.js → dist-DMEDEJ2D.js} +40 -4
- package/dist/index.js +412 -64
- 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 +3 -3
- package/dist/chunk-EIIG7J6V.js.map +0 -1
- package/dist/web/assets/index-DJuE5oTj.js +0 -68
- package/dist/web/assets/index-gSYgPU_H.css +0 -2
- /package/dist/{dist-2YXXOCZJ.js.map → dist-DMEDEJ2D.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,20 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
BookmarkStorageUnavailableError,
|
|
4
|
+
classifySessionTags,
|
|
5
|
+
computeIdentity,
|
|
3
6
|
createRegisteredAgents,
|
|
4
7
|
deleteBookmark,
|
|
5
8
|
filterSessions,
|
|
6
9
|
getAgentInfoMap,
|
|
7
10
|
getCursorDataPath,
|
|
11
|
+
getSmartTagSourceTimestamp,
|
|
8
12
|
importBookmarks,
|
|
9
13
|
listBookmarks,
|
|
14
|
+
listCachedProjectGroups,
|
|
10
15
|
perf,
|
|
16
|
+
realFs,
|
|
17
|
+
refreshPricingCache,
|
|
11
18
|
resolveProviderRoots,
|
|
12
19
|
saveCachedSessions,
|
|
13
20
|
scanSessions,
|
|
14
21
|
searchSessions,
|
|
15
22
|
syncSessionSearchIndex,
|
|
16
23
|
upsertBookmark
|
|
17
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-FZNZAMTZ.js";
|
|
18
25
|
|
|
19
26
|
// src/index.ts
|
|
20
27
|
import { defineCommand, runMain } from "citty";
|
|
@@ -23,14 +30,144 @@ import { defineCommand, runMain } from "citty";
|
|
|
23
30
|
import { Hono as Hono2 } from "hono";
|
|
24
31
|
import { serve } from "@hono/node-server";
|
|
25
32
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
26
|
-
import {
|
|
27
|
-
import { existsSync } from "fs";
|
|
33
|
+
import { existsSync as existsSync2 } from "fs";
|
|
28
34
|
import { resolve, dirname } from "path";
|
|
29
35
|
import { fileURLToPath } from "url";
|
|
30
36
|
|
|
31
37
|
// src/api/routes.ts
|
|
32
38
|
import { Hono } from "hono";
|
|
33
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
|
+
|
|
34
171
|
// src/api/handlers.ts
|
|
35
172
|
function isRecord(value) {
|
|
36
173
|
return typeof value === "object" && value !== null;
|
|
@@ -78,6 +215,24 @@ function filterSessionsByActivityWindow(sessions, from, to) {
|
|
|
78
215
|
return true;
|
|
79
216
|
});
|
|
80
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
|
+
}
|
|
81
236
|
function handleGetConfig(c, defaults) {
|
|
82
237
|
return c.json({
|
|
83
238
|
window: {
|
|
@@ -99,11 +254,22 @@ function handleGetAgents(c, scanSource, defaults = {}) {
|
|
|
99
254
|
const info = getAgentInfoMap(counts);
|
|
100
255
|
return c.json(info);
|
|
101
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
|
+
}
|
|
102
266
|
function handleGetSessions(c, scanSource, defaults = {}) {
|
|
103
267
|
const scanResult = scanSource.getSnapshot();
|
|
104
268
|
const agent = c.req.query("agent");
|
|
105
269
|
const q = c.req.query("q")?.toLowerCase();
|
|
106
|
-
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();
|
|
107
273
|
const from = parseDateParam(c.req.query("from"), defaults.from);
|
|
108
274
|
const to = parseDateParam(c.req.query("to"), defaults.to);
|
|
109
275
|
let sessions = [];
|
|
@@ -112,10 +278,15 @@ function handleGetSessions(c, scanSource, defaults = {}) {
|
|
|
112
278
|
} else {
|
|
113
279
|
sessions = [...scanResult.sessions];
|
|
114
280
|
}
|
|
115
|
-
if (
|
|
116
|
-
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));
|
|
117
285
|
}
|
|
118
286
|
sessions = filterSessionsByActivityWindow(sessions, from, to);
|
|
287
|
+
if (tag) {
|
|
288
|
+
sessions = sessions.filter((s) => s.smart_tags?.includes(tag));
|
|
289
|
+
}
|
|
119
290
|
if (q) {
|
|
120
291
|
sessions = sessions.filter((s) => s.title.toLowerCase().includes(q));
|
|
121
292
|
}
|
|
@@ -149,6 +320,7 @@ function handleSearchSessions(c, scanSource, defaults = {}) {
|
|
|
149
320
|
return c.json({ results });
|
|
150
321
|
}
|
|
151
322
|
async function handleGetSessionData(c, scanSource) {
|
|
323
|
+
const startedAt = performance.now();
|
|
152
324
|
const scanResult = scanSource.getSnapshot();
|
|
153
325
|
const agentName = c.req.param("agent");
|
|
154
326
|
const sessionId = c.req.param("id");
|
|
@@ -160,22 +332,71 @@ async function handleGetSessionData(c, scanSource) {
|
|
|
160
332
|
return c.json({ error: `Unknown agent: ${agentName}` }, 404);
|
|
161
333
|
}
|
|
162
334
|
try {
|
|
335
|
+
const loadStartedAt = performance.now();
|
|
163
336
|
const data = agent.getSessionData(sessionId);
|
|
164
|
-
|
|
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
|
+
});
|
|
165
356
|
} catch (err) {
|
|
166
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
|
+
});
|
|
167
364
|
return c.json({ error: message }, 500);
|
|
168
365
|
}
|
|
169
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
|
+
}
|
|
170
377
|
function handleGetBookmarks(c) {
|
|
171
|
-
|
|
378
|
+
try {
|
|
379
|
+
return c.json({ bookmarks: listBookmarks(), storageAvailable: true });
|
|
380
|
+
} catch (error) {
|
|
381
|
+
if (error instanceof BookmarkStorageUnavailableError) {
|
|
382
|
+
return c.json({ bookmarks: [], storageAvailable: false });
|
|
383
|
+
}
|
|
384
|
+
throw error;
|
|
385
|
+
}
|
|
172
386
|
}
|
|
173
387
|
async function handlePutBookmark(c) {
|
|
174
388
|
const payload = parseBookmarkPayload(await c.req.json().catch(() => null));
|
|
175
389
|
if (!payload) {
|
|
176
390
|
return c.json({ error: "Invalid bookmark payload" }, 400);
|
|
177
391
|
}
|
|
178
|
-
|
|
392
|
+
try {
|
|
393
|
+
return c.json({ bookmark: upsertBookmark(payload), storageAvailable: true });
|
|
394
|
+
} catch (error) {
|
|
395
|
+
if (error instanceof BookmarkStorageUnavailableError) {
|
|
396
|
+
return c.json({ error: "Bookmark storage is unavailable" }, 503);
|
|
397
|
+
}
|
|
398
|
+
throw error;
|
|
399
|
+
}
|
|
179
400
|
}
|
|
180
401
|
async function handleImportBookmarks(c) {
|
|
181
402
|
const payload = await c.req.json().catch(() => null);
|
|
@@ -186,7 +407,14 @@ async function handleImportBookmarks(c) {
|
|
|
186
407
|
if (bookmarks.length !== payload.length) {
|
|
187
408
|
return c.json({ error: "Invalid bookmark payload" }, 400);
|
|
188
409
|
}
|
|
189
|
-
|
|
410
|
+
try {
|
|
411
|
+
return c.json({ bookmarks: importBookmarks(bookmarks), storageAvailable: true });
|
|
412
|
+
} catch (error) {
|
|
413
|
+
if (error instanceof BookmarkStorageUnavailableError) {
|
|
414
|
+
return c.json({ error: "Bookmark storage is unavailable" }, 503);
|
|
415
|
+
}
|
|
416
|
+
throw error;
|
|
417
|
+
}
|
|
190
418
|
}
|
|
191
419
|
function handleDeleteBookmark(c) {
|
|
192
420
|
const agentKey = c.req.param("agent");
|
|
@@ -194,8 +422,15 @@ function handleDeleteBookmark(c) {
|
|
|
194
422
|
if (!agentKey || !sessionId) {
|
|
195
423
|
return c.json({ error: "Missing bookmark identifier" }, 400);
|
|
196
424
|
}
|
|
197
|
-
|
|
198
|
-
|
|
425
|
+
try {
|
|
426
|
+
deleteBookmark(agentKey, sessionId);
|
|
427
|
+
return c.json({ ok: true, storageAvailable: true });
|
|
428
|
+
} catch (error) {
|
|
429
|
+
if (error instanceof BookmarkStorageUnavailableError) {
|
|
430
|
+
return c.json({ error: "Bookmark storage is unavailable" }, 503);
|
|
431
|
+
}
|
|
432
|
+
throw error;
|
|
433
|
+
}
|
|
199
434
|
}
|
|
200
435
|
function toLocalDateKey(ts) {
|
|
201
436
|
const d = new Date(ts);
|
|
@@ -211,23 +446,22 @@ function startOfLocalDay(ts) {
|
|
|
211
446
|
}
|
|
212
447
|
function resolveDashboardWindow(defaults, queryDays, queryFrom, queryTo) {
|
|
213
448
|
const now = Date.now();
|
|
214
|
-
const
|
|
215
|
-
const toTs = parseDateParam(queryTo, defaults.to) ?? todayStart + 24 * 60 * 60 * 1e3 - 1;
|
|
449
|
+
const toTs = parseDateParam(queryTo, defaults.to) ?? now;
|
|
216
450
|
const parsedDays = queryDays ? parseInt(queryDays, 10) : NaN;
|
|
217
451
|
let days = Number.isFinite(parsedDays) && parsedDays > 0 ? parsedDays : defaults.days;
|
|
218
452
|
const fromFromQuery = parseDateParam(queryFrom, void 0);
|
|
219
453
|
let fromTs;
|
|
220
454
|
if (fromFromQuery != null) {
|
|
221
|
-
fromTs =
|
|
222
|
-
days ??= Math.max(1, Math.ceil((
|
|
223
|
-
} else if (days && days > 0) {
|
|
224
|
-
fromTs = todayStart - (days - 1) * 864e5;
|
|
455
|
+
fromTs = fromFromQuery;
|
|
456
|
+
days ??= Math.max(1, Math.ceil((toTs - fromTs) / 864e5));
|
|
225
457
|
} else if (defaults.from != null) {
|
|
226
|
-
fromTs =
|
|
227
|
-
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;
|
|
228
462
|
} else {
|
|
229
463
|
days = 30;
|
|
230
|
-
fromTs =
|
|
464
|
+
fromTs = startOfLocalDay(toTs) - (days - 1) * 864e5;
|
|
231
465
|
}
|
|
232
466
|
return { from: fromTs, to: toTs, days };
|
|
233
467
|
}
|
|
@@ -252,11 +486,13 @@ function handleGetDashboard(c, scanSource, defaults = {}) {
|
|
|
252
486
|
let totalMessages = 0;
|
|
253
487
|
let totalTokens = 0;
|
|
254
488
|
let totalCost = 0;
|
|
489
|
+
let hasEstimatedCost = false;
|
|
255
490
|
let latestActivity = 0;
|
|
256
491
|
for (const session of windowed) {
|
|
257
492
|
totalMessages += session.stats.message_count;
|
|
258
493
|
totalTokens += getTotalTokens(session.stats);
|
|
259
494
|
totalCost += session.stats.total_cost ?? 0;
|
|
495
|
+
if (session.stats.cost_source === "estimated") hasEstimatedCost = true;
|
|
260
496
|
const activity = getSessionActivityTime(session);
|
|
261
497
|
if (activity > latestActivity) latestActivity = activity;
|
|
262
498
|
}
|
|
@@ -281,7 +517,8 @@ function handleGetDashboard(c, scanSource, defaults = {}) {
|
|
|
281
517
|
const dailyMap = /* @__PURE__ */ new Map();
|
|
282
518
|
const dailyTokenMap = /* @__PURE__ */ new Map();
|
|
283
519
|
const bucketStart = startOfLocalDay(from);
|
|
284
|
-
|
|
520
|
+
const bucketDays = Math.floor((startOfLocalDay(to) - bucketStart) / 864e5) + 1;
|
|
521
|
+
for (let i = 0; i < bucketDays; i += 1) {
|
|
285
522
|
const ts = bucketStart + i * 864e5;
|
|
286
523
|
const key = toLocalDateKey(ts);
|
|
287
524
|
dailyMap.set(key, { date: key, sessions: 0, messages: 0 });
|
|
@@ -330,6 +567,7 @@ function handleGetDashboard(c, scanSource, defaults = {}) {
|
|
|
330
567
|
messages: totalMessages,
|
|
331
568
|
tokens: totalTokens,
|
|
332
569
|
cost: totalCost,
|
|
570
|
+
cost_source: totalCost > 0 ? hasEstimatedCost ? "estimated" : "recorded" : void 0,
|
|
333
571
|
latestActivity: latestActivity || void 0
|
|
334
572
|
},
|
|
335
573
|
perAgent,
|
|
@@ -391,6 +629,7 @@ function createApiRoutes(scanSource, store, options = {}) {
|
|
|
391
629
|
};
|
|
392
630
|
api.get("/config", (c) => handleGetConfig(c, listDefaults));
|
|
393
631
|
api.get("/agents", (c) => handleGetAgents(c, scanSource, listDefaults));
|
|
632
|
+
api.get("/projects", (c) => handleGetProjects(c, scanSource, listDefaults));
|
|
394
633
|
api.get("/sessions", (c) => handleGetSessions(c, scanSource, listDefaults));
|
|
395
634
|
api.get("/search", (c) => handleSearchSessions(c, scanSource, listDefaults));
|
|
396
635
|
api.get("/sessions/:agent/:id", (c) => handleGetSessionData(c, scanSource));
|
|
@@ -399,6 +638,7 @@ function createApiRoutes(scanSource, store, options = {}) {
|
|
|
399
638
|
api.put("/bookmarks", (c) => handlePutBookmark(c));
|
|
400
639
|
api.post("/bookmarks/import", (c) => handleImportBookmarks(c));
|
|
401
640
|
api.delete("/bookmarks/:agent/:id", (c) => handleDeleteBookmark(c));
|
|
641
|
+
api.post("/logs", (c) => handlePostClientLog(c));
|
|
402
642
|
if (store) {
|
|
403
643
|
api.get("/events", (c) => createSseResponse(store, c.req.raw.signal));
|
|
404
644
|
}
|
|
@@ -409,11 +649,11 @@ function createApiRoutes(scanSource, store, options = {}) {
|
|
|
409
649
|
function findWebDistPath() {
|
|
410
650
|
const __dirname2 = dirname(fileURLToPath(import.meta.url));
|
|
411
651
|
const packagedPath = resolve(__dirname2, "web");
|
|
412
|
-
if (
|
|
652
|
+
if (existsSync2(packagedPath)) {
|
|
413
653
|
return packagedPath;
|
|
414
654
|
}
|
|
415
655
|
const devPath = resolve(__dirname2, "../../../apps/web/dist");
|
|
416
|
-
if (
|
|
656
|
+
if (existsSync2(devPath)) {
|
|
417
657
|
return devPath;
|
|
418
658
|
}
|
|
419
659
|
return null;
|
|
@@ -440,7 +680,26 @@ function getServerStartupErrorMessage(error, port) {
|
|
|
440
680
|
}
|
|
441
681
|
async function createServer(port, store, options = {}) {
|
|
442
682
|
const app = new Hono2();
|
|
443
|
-
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
|
+
});
|
|
444
703
|
const routeOptions = {
|
|
445
704
|
defaultSessionFrom: options.defaultSessionFrom,
|
|
446
705
|
defaultSessionTo: options.defaultSessionTo,
|
|
@@ -463,6 +722,7 @@ async function createServer(port, store, options = {}) {
|
|
|
463
722
|
try {
|
|
464
723
|
await waitForListening(server);
|
|
465
724
|
} catch (error) {
|
|
725
|
+
appLogger.error("server.listen.error", { port, error });
|
|
466
726
|
server.close();
|
|
467
727
|
if (store.shutdown) {
|
|
468
728
|
await store.shutdown();
|
|
@@ -470,9 +730,11 @@ async function createServer(port, store, options = {}) {
|
|
|
470
730
|
throw new Error(getServerStartupErrorMessage(error, port));
|
|
471
731
|
}
|
|
472
732
|
const url = `http://localhost:${port}`;
|
|
733
|
+
appLogger.info("server.listen", { port, url });
|
|
473
734
|
return {
|
|
474
735
|
url,
|
|
475
736
|
shutdown: () => {
|
|
737
|
+
appLogger.info("server.shutdown", { port });
|
|
476
738
|
server.close();
|
|
477
739
|
if (store.shutdown) {
|
|
478
740
|
void store.shutdown();
|
|
@@ -482,8 +744,8 @@ async function createServer(port, store, options = {}) {
|
|
|
482
744
|
}
|
|
483
745
|
|
|
484
746
|
// src/live-scan.ts
|
|
485
|
-
import { existsSync as
|
|
486
|
-
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";
|
|
487
749
|
import chokidar from "chokidar";
|
|
488
750
|
function sortSessions(sessions) {
|
|
489
751
|
return [...sessions].sort(
|
|
@@ -547,11 +809,11 @@ function buildUpdateEvent(agentName, previousSessions, nextSessions) {
|
|
|
547
809
|
};
|
|
548
810
|
}
|
|
549
811
|
function closestWatchablePath(targetPath) {
|
|
550
|
-
if (!isAbsolute(targetPath) && !
|
|
812
|
+
if (!isAbsolute(targetPath) && !existsSync3(targetPath)) {
|
|
551
813
|
return null;
|
|
552
814
|
}
|
|
553
815
|
let current = targetPath;
|
|
554
|
-
while (!
|
|
816
|
+
while (!existsSync3(current)) {
|
|
555
817
|
const parent = dirname2(current);
|
|
556
818
|
if (parent === current) {
|
|
557
819
|
return null;
|
|
@@ -566,24 +828,24 @@ function resolveAgentWatchTargets(agentName) {
|
|
|
566
828
|
switch (agentName) {
|
|
567
829
|
case "claudecode":
|
|
568
830
|
return [
|
|
569
|
-
{ path:
|
|
831
|
+
{ path: join2(roots.claudeRoot, "projects"), depth: 2 },
|
|
570
832
|
{ path: "data/claudecode", depth: 2 }
|
|
571
833
|
];
|
|
572
834
|
case "codex":
|
|
573
|
-
return [{ path:
|
|
835
|
+
return [{ path: join2(roots.codexRoot, "sessions"), depth: 4 }];
|
|
574
836
|
case "cursor":
|
|
575
837
|
return cursorDataPath ? [
|
|
576
|
-
{ path:
|
|
577
|
-
{ path:
|
|
838
|
+
{ path: join2(cursorDataPath, "globalStorage", "state.vscdb") },
|
|
839
|
+
{ path: join2(cursorDataPath, "workspaceStorage"), depth: 2 }
|
|
578
840
|
] : [];
|
|
579
841
|
case "kimi":
|
|
580
842
|
return [
|
|
581
|
-
{ path:
|
|
843
|
+
{ path: join2(roots.kimiRoot, "sessions"), depth: 2 },
|
|
582
844
|
{ path: "data/kimi", depth: 2 }
|
|
583
845
|
];
|
|
584
846
|
case "opencode":
|
|
585
847
|
return [
|
|
586
|
-
{ path:
|
|
848
|
+
{ path: join2(roots.opencodeRoot, "opencode.db") },
|
|
587
849
|
{ path: "data/opencode/opencode.db" }
|
|
588
850
|
];
|
|
589
851
|
default:
|
|
@@ -591,12 +853,14 @@ function resolveAgentWatchTargets(agentName) {
|
|
|
591
853
|
}
|
|
592
854
|
}
|
|
593
855
|
var LiveScanStore = class {
|
|
594
|
-
constructor(watchEnabled = true, scanOptions = {}) {
|
|
856
|
+
constructor(watchEnabled = true, scanOptions = {}, startupScanOptions = {}) {
|
|
595
857
|
this.watchEnabled = watchEnabled;
|
|
596
858
|
this.scanOptions = scanOptions;
|
|
859
|
+
this.startupScanOptions = startupScanOptions;
|
|
597
860
|
}
|
|
598
861
|
watchEnabled;
|
|
599
862
|
scanOptions;
|
|
863
|
+
startupScanOptions;
|
|
600
864
|
agents = [];
|
|
601
865
|
byAgent = {};
|
|
602
866
|
sessions = [];
|
|
@@ -607,33 +871,30 @@ var LiveScanStore = class {
|
|
|
607
871
|
pendingRefreshes = /* @__PURE__ */ new Set();
|
|
608
872
|
watchers = [];
|
|
609
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
|
+
});
|
|
610
882
|
const initialResult = await scanSessions({
|
|
611
883
|
...this.scanOptions,
|
|
612
|
-
|
|
613
|
-
|
|
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
|
|
614
889
|
});
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
if (!agentMap.has(agent.name)) {
|
|
623
|
-
agentMap.set(agent.name, agent);
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
this.agents = [...agentMap.values()].filter((agent) => {
|
|
627
|
-
if (!allowedAgents) {
|
|
628
|
-
return true;
|
|
629
|
-
}
|
|
630
|
-
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
|
+
)
|
|
631
897
|
});
|
|
632
|
-
for (const agent of this.agents) {
|
|
633
|
-
this.byAgent[agent.name] = sortSessions(initialResult.byAgent[agent.name] ?? []);
|
|
634
|
-
this.refreshTimestamps.set(agent.name, Date.now());
|
|
635
|
-
}
|
|
636
|
-
this.rebuildSessions();
|
|
637
898
|
if (this.watchEnabled) {
|
|
638
899
|
this.startWatching();
|
|
639
900
|
}
|
|
@@ -667,6 +928,34 @@ var LiveScanStore = class {
|
|
|
667
928
|
rebuildSessions() {
|
|
668
929
|
this.sessions = sortSessions(Object.values(this.byAgent).flat());
|
|
669
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
|
+
}
|
|
670
959
|
getAllowedAgents() {
|
|
671
960
|
if (!this.scanOptions.agents?.length) {
|
|
672
961
|
return null;
|
|
@@ -674,7 +963,7 @@ var LiveScanStore = class {
|
|
|
674
963
|
return new Set(this.scanOptions.agents.map((agent) => agent.toLowerCase()));
|
|
675
964
|
}
|
|
676
965
|
applyFilters(sessions) {
|
|
677
|
-
return filterSessions(sessions, this.scanOptions);
|
|
966
|
+
return filterSessions(sessions, { ...this.scanOptions, ...this.startupScanOptions });
|
|
678
967
|
}
|
|
679
968
|
startWatching() {
|
|
680
969
|
for (const agent of this.agents) {
|
|
@@ -686,8 +975,16 @@ var LiveScanStore = class {
|
|
|
686
975
|
(target, index, items) => items.findIndex((item) => item.path === target.path && item.depth === target.depth) === index
|
|
687
976
|
);
|
|
688
977
|
if (watchTargets.length === 0) {
|
|
978
|
+
appLogger.debug("watch.skip", { agent: agent.name });
|
|
689
979
|
continue;
|
|
690
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
|
+
});
|
|
691
988
|
const watcher = chokidar.watch(
|
|
692
989
|
watchTargets.map((target) => target.path),
|
|
693
990
|
{
|
|
@@ -706,12 +1003,14 @@ var LiveScanStore = class {
|
|
|
706
1003
|
this.scheduleRefresh(agent.name);
|
|
707
1004
|
});
|
|
708
1005
|
watcher.on("error", (error) => {
|
|
1006
|
+
appLogger.error("watch.error", { agent: agent.name, error });
|
|
709
1007
|
console.error(`[${agent.name}] File watcher failed:`, error);
|
|
710
1008
|
});
|
|
711
1009
|
this.watchers.push(watcher);
|
|
712
1010
|
}
|
|
713
1011
|
}
|
|
714
1012
|
scheduleRefresh(agentName, delayMs = 200) {
|
|
1013
|
+
appLogger.debug("scan.refresh.schedule", { agent: agentName, delay_ms: delayMs });
|
|
715
1014
|
const existing = this.refreshTimers.get(agentName);
|
|
716
1015
|
if (existing) {
|
|
717
1016
|
clearTimeout(existing);
|
|
@@ -724,6 +1023,7 @@ var LiveScanStore = class {
|
|
|
724
1023
|
}
|
|
725
1024
|
async refreshAgent(agentName) {
|
|
726
1025
|
if (this.refreshInFlight.has(agentName)) {
|
|
1026
|
+
appLogger.debug("scan.refresh.pending", { agent: agentName });
|
|
727
1027
|
this.pendingRefreshes.add(agentName);
|
|
728
1028
|
return;
|
|
729
1029
|
}
|
|
@@ -738,8 +1038,10 @@ var LiveScanStore = class {
|
|
|
738
1038
|
}
|
|
739
1039
|
}
|
|
740
1040
|
async runRefresh(agentName) {
|
|
1041
|
+
const startedAt = performance.now();
|
|
741
1042
|
const agent = this.agents.find((item) => item.name === agentName);
|
|
742
1043
|
if (!agent) {
|
|
1044
|
+
appLogger.warn("scan.refresh.missing_agent", { agent: agentName });
|
|
743
1045
|
return;
|
|
744
1046
|
}
|
|
745
1047
|
const previousSessions = this.byAgent[agentName] ?? [];
|
|
@@ -753,17 +1055,23 @@ var LiveScanStore = class {
|
|
|
753
1055
|
);
|
|
754
1056
|
this.refreshTimestamps.set(agentName, checkResult.timestamp);
|
|
755
1057
|
if (!checkResult.hasChanges) {
|
|
1058
|
+
appLogger.debug("scan.refresh.unchanged", {
|
|
1059
|
+
agent: agentName,
|
|
1060
|
+
duration_ms: Math.round(performance.now() - startedAt)
|
|
1061
|
+
});
|
|
756
1062
|
return;
|
|
757
1063
|
}
|
|
758
1064
|
nextSessions = await Promise.resolve(
|
|
759
1065
|
agent.incrementalScan(previousSessions, checkResult.changedIds ?? [])
|
|
760
1066
|
);
|
|
761
1067
|
} else {
|
|
762
|
-
nextSessions = await Promise.resolve(agent.scan());
|
|
1068
|
+
nextSessions = await Promise.resolve(agent.scan(this.startupScanOptions));
|
|
763
1069
|
this.refreshTimestamps.set(agentName, Date.now());
|
|
764
1070
|
}
|
|
765
1071
|
nextSessions = this.applyFilters(nextSessions);
|
|
766
|
-
|
|
1072
|
+
if (!this.hasStartupWindow()) {
|
|
1073
|
+
saveCachedSessions(agentName, nextSessions, buildAgentCacheMeta(agent));
|
|
1074
|
+
}
|
|
767
1075
|
syncSessionSearchIndex(agentName, nextSessions, (sessionId) => agent.getSessionData(sessionId));
|
|
768
1076
|
const event = buildUpdateEvent(agentName, previousSessions, nextSessions);
|
|
769
1077
|
this.byAgent[agentName] = sortSessions(nextSessions);
|
|
@@ -772,6 +1080,14 @@ var LiveScanStore = class {
|
|
|
772
1080
|
event.totalSessions = this.sessions.length;
|
|
773
1081
|
this.emit(event);
|
|
774
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
|
+
});
|
|
775
1091
|
}
|
|
776
1092
|
};
|
|
777
1093
|
|
|
@@ -907,6 +1223,7 @@ var main = defineCommand({
|
|
|
907
1223
|
}
|
|
908
1224
|
},
|
|
909
1225
|
async run({ args }) {
|
|
1226
|
+
const startedAt = performance.now();
|
|
910
1227
|
const port = parseInt(args.port, 10) || 4321;
|
|
911
1228
|
const noOpen = args.noOpen;
|
|
912
1229
|
const jsonOnly = args.json;
|
|
@@ -916,11 +1233,22 @@ var main = defineCommand({
|
|
|
916
1233
|
if (trace) {
|
|
917
1234
|
perf.enable();
|
|
918
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
|
+
});
|
|
919
1245
|
if (clearCache) {
|
|
920
|
-
const { clearCache: clear } = await import("./dist-
|
|
1246
|
+
const { clearCache: clear } = await import("./dist-DMEDEJ2D.js");
|
|
921
1247
|
clear();
|
|
1248
|
+
appLogger.info("cache.clear");
|
|
922
1249
|
console.log("Cache cleared.");
|
|
923
1250
|
}
|
|
1251
|
+
void refreshPricingCache();
|
|
924
1252
|
let targetSession = null;
|
|
925
1253
|
if (args.session) {
|
|
926
1254
|
targetSession = parseSessionUri(args.session);
|
|
@@ -950,9 +1278,19 @@ var main = defineCommand({
|
|
|
950
1278
|
cwd: cwdFilter,
|
|
951
1279
|
useCache
|
|
952
1280
|
};
|
|
953
|
-
const
|
|
1281
|
+
const startupScanOptions = targetSession || jsonOnly ? {} : { from: listDefaultFrom, to: listDefaultTo };
|
|
1282
|
+
const store = new LiveScanStore(!jsonOnly, scanOptions, startupScanOptions);
|
|
954
1283
|
await store.initialize();
|
|
955
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
|
+
});
|
|
956
1294
|
if (trace) {
|
|
957
1295
|
console.log(perf.getReport());
|
|
958
1296
|
}
|
|
@@ -975,6 +1313,10 @@ var main = defineCommand({
|
|
|
975
1313
|
})),
|
|
976
1314
|
sessions: windowed
|
|
977
1315
|
};
|
|
1316
|
+
appLogger.info("cli.json_output", {
|
|
1317
|
+
sessions: windowed.length,
|
|
1318
|
+
duration_ms: Math.round(performance.now() - startedAt)
|
|
1319
|
+
});
|
|
978
1320
|
console.log(JSON.stringify(output, null, 2));
|
|
979
1321
|
return;
|
|
980
1322
|
}
|
|
@@ -993,9 +1335,15 @@ var main = defineCommand({
|
|
|
993
1335
|
}
|
|
994
1336
|
console.log(` ${url}`);
|
|
995
1337
|
console.log("");
|
|
1338
|
+
appLogger.info("cli.ready", {
|
|
1339
|
+
url,
|
|
1340
|
+
duration_ms: Math.round(performance.now() - startedAt),
|
|
1341
|
+
log_path: appLogger.getLogPath()
|
|
1342
|
+
});
|
|
996
1343
|
if (!noOpen) {
|
|
997
1344
|
const open = (await import("open")).default;
|
|
998
1345
|
const targetUrl = targetSession ? `${url}/${targetSession.agent.toLowerCase()}/${targetSession.sessionId}` : url;
|
|
1346
|
+
appLogger.info("browser.open", { url: targetUrl });
|
|
999
1347
|
await open(targetUrl);
|
|
1000
1348
|
}
|
|
1001
1349
|
}
|