@cookielab.io/klovi 3.3.0 → 3.4.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/cli.js +452 -128
- package/dist/server.js +452 -128
- package/dist/web/{chunk-xhqm59y9.js → chunk-1kc2497n.js} +165 -165
- package/dist/web/chunk-psyp4t4q.css +1 -0
- package/dist/web/index.html +1 -1
- package/package.json +2 -2
- package/dist/web/chunk-v551jxfx.css +0 -1
package/dist/server.js
CHANGED
|
@@ -223,13 +223,23 @@ class PluginRegistry {
|
|
|
223
223
|
getAllPlugins() {
|
|
224
224
|
return [...this.plugins.values()].map((entry) => entry.plugin);
|
|
225
225
|
}
|
|
226
|
-
|
|
226
|
+
discoverPluginStates(includeSessions) {
|
|
227
227
|
return Effect2.gen(this, function* () {
|
|
228
|
-
const
|
|
229
|
-
for (const
|
|
230
|
-
const
|
|
231
|
-
|
|
228
|
+
const states = [];
|
|
229
|
+
for (const entry of this.plugins.values()) {
|
|
230
|
+
const discoveredIndex = includeSessions && entry.plugin.discoverIndex ? yield* entry.plugin.discoverIndex.pipe(Effect2.provide(entry.configLayer), Effect2.catchAll(() => Effect2.succeed(undefined))) : undefined;
|
|
231
|
+
const projects = discoveredIndex?.projects ?? (yield* entry.plugin.discoverProjects.pipe(Effect2.provide(entry.configLayer), Effect2.catchAll(() => Effect2.succeed([]))));
|
|
232
|
+
states.push({
|
|
233
|
+
entry,
|
|
234
|
+
projects,
|
|
235
|
+
...discoveredIndex ? { sessionsByNativeId: discoveredIndex.sessionsByNativeId } : {}
|
|
236
|
+
});
|
|
232
237
|
}
|
|
238
|
+
return states;
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
mergeProjects(allProjects) {
|
|
242
|
+
return Effect2.gen(function* () {
|
|
233
243
|
yield* resolveT3CodePaths(allProjects);
|
|
234
244
|
const projectsByPath = new Map;
|
|
235
245
|
for (const project of allProjects) {
|
|
@@ -259,6 +269,50 @@ class PluginRegistry {
|
|
|
259
269
|
return merged;
|
|
260
270
|
});
|
|
261
271
|
}
|
|
272
|
+
encodeSessions(pluginId, sessions) {
|
|
273
|
+
return sessions.map((session) => ({
|
|
274
|
+
...session,
|
|
275
|
+
sessionId: this.sessionIdEncoder(pluginId, session.sessionId),
|
|
276
|
+
pluginId
|
|
277
|
+
}));
|
|
278
|
+
}
|
|
279
|
+
loadSourceSessions(state, source) {
|
|
280
|
+
const discoveredSessions = state.sessionsByNativeId?.get(source.nativeId);
|
|
281
|
+
if (discoveredSessions) {
|
|
282
|
+
return Effect2.succeed(this.encodeSessions(source.pluginId, discoveredSessions));
|
|
283
|
+
}
|
|
284
|
+
return state.entry.plugin.listSessions(source.nativeId).pipe(Effect2.provide(state.entry.configLayer), Effect2.catchAll(() => Effect2.succeed([])), Effect2.map((sessions) => this.encodeSessions(source.pluginId, sessions)));
|
|
285
|
+
}
|
|
286
|
+
discoverAllProjects() {
|
|
287
|
+
return Effect2.gen(this, function* () {
|
|
288
|
+
const states = yield* this.discoverPluginStates(false);
|
|
289
|
+
return yield* this.mergeProjects(states.flatMap((state) => state.projects));
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
discoverAllProjectsWithSessions() {
|
|
293
|
+
return Effect2.gen(this, function* () {
|
|
294
|
+
const states = yield* this.discoverPluginStates(true);
|
|
295
|
+
const mergedProjects = yield* this.mergeProjects(states.flatMap((state) => state.projects));
|
|
296
|
+
const statesByPluginId = new Map(states.map((state) => [state.entry.plugin.id, state]));
|
|
297
|
+
const sessionsByEncodedPath = new Map;
|
|
298
|
+
for (const project of mergedProjects) {
|
|
299
|
+
const allSessions = [];
|
|
300
|
+
for (const source of project.sources) {
|
|
301
|
+
const state = statesByPluginId.get(source.pluginId);
|
|
302
|
+
if (!state) {
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
allSessions.push(...yield* this.loadSourceSessions(state, source));
|
|
306
|
+
}
|
|
307
|
+
sortByIsoDesc(allSessions, (session) => session.timestamp);
|
|
308
|
+
sessionsByEncodedPath.set(project.encodedPath, allSessions);
|
|
309
|
+
}
|
|
310
|
+
return {
|
|
311
|
+
projects: mergedProjects,
|
|
312
|
+
sessionsByEncodedPath
|
|
313
|
+
};
|
|
314
|
+
});
|
|
315
|
+
}
|
|
262
316
|
listAllSessions(project) {
|
|
263
317
|
return Effect2.gen(this, function* () {
|
|
264
318
|
const allSessions = [];
|
|
@@ -267,12 +321,7 @@ class PluginRegistry {
|
|
|
267
321
|
if (!entry) {
|
|
268
322
|
continue;
|
|
269
323
|
}
|
|
270
|
-
|
|
271
|
-
allSessions.push(...sessions.map((session) => ({
|
|
272
|
-
...session,
|
|
273
|
-
sessionId: this.sessionIdEncoder(source.pluginId, session.sessionId),
|
|
274
|
-
pluginId: source.pluginId
|
|
275
|
-
})));
|
|
324
|
+
allSessions.push(...yield* entry.plugin.listSessions(source.nativeId).pipe(Effect2.provide(entry.configLayer), Effect2.catchAll(() => Effect2.succeed([])), Effect2.map((sessions) => this.encodeSessions(source.pluginId, sessions))));
|
|
276
325
|
}
|
|
277
326
|
sortByIsoDesc(allSessions, (session) => session.timestamp);
|
|
278
327
|
return allSessions;
|
|
@@ -880,7 +929,7 @@ var init_platform_node = __esm(() => {
|
|
|
880
929
|
import { execFile } from "node:child_process";
|
|
881
930
|
|
|
882
931
|
// ../../packages/server/src/effect/bootstrap.ts
|
|
883
|
-
import { join as
|
|
932
|
+
import { join as join15 } from "node:path";
|
|
884
933
|
import { HttpServer } from "@effect/platform";
|
|
885
934
|
import { Cause, Effect as Effect32, Fiber, Layer as Layer8 } from "effect";
|
|
886
935
|
|
|
@@ -2773,6 +2822,15 @@ function extractFirstUserTextFromHeaders(db, composerId, headers) {
|
|
|
2773
2822
|
}
|
|
2774
2823
|
return "";
|
|
2775
2824
|
}
|
|
2825
|
+
function resolveComposerFallbackMessage(composer) {
|
|
2826
|
+
if (isNonEmptyString(composer.name)) {
|
|
2827
|
+
return composer.name.trim();
|
|
2828
|
+
}
|
|
2829
|
+
if (isNonEmptyString(composer.subtitle)) {
|
|
2830
|
+
return composer.subtitle.trim();
|
|
2831
|
+
}
|
|
2832
|
+
return "Cursor session";
|
|
2833
|
+
}
|
|
2776
2834
|
function resolveComposerFirstMessage(globalDb, composer, composerId) {
|
|
2777
2835
|
if (globalDb) {
|
|
2778
2836
|
const rawComposerData = queryKeyValueRow(globalDb, `composerData:${composerId}`);
|
|
@@ -2786,13 +2844,7 @@ function resolveComposerFirstMessage(globalDb, composer, composerId) {
|
|
|
2786
2844
|
return truncate(fromHeaders, SESSION_PREVIEW_MAX_LENGTH);
|
|
2787
2845
|
}
|
|
2788
2846
|
}
|
|
2789
|
-
|
|
2790
|
-
return composer.name.trim();
|
|
2791
|
-
}
|
|
2792
|
-
if (isNonEmptyString(composer.subtitle)) {
|
|
2793
|
-
return composer.subtitle.trim();
|
|
2794
|
-
}
|
|
2795
|
-
return "Cursor session";
|
|
2847
|
+
return resolveComposerFallbackMessage(composer);
|
|
2796
2848
|
}
|
|
2797
2849
|
function resolveComposerSessionType(unifiedMode) {
|
|
2798
2850
|
if (unifiedMode === "plan") {
|
|
@@ -2803,7 +2855,13 @@ function resolveComposerSessionType(unifiedMode) {
|
|
|
2803
2855
|
}
|
|
2804
2856
|
return;
|
|
2805
2857
|
}
|
|
2806
|
-
function createComposerSummary(
|
|
2858
|
+
function createComposerSummary({
|
|
2859
|
+
projectPath,
|
|
2860
|
+
workspaceDbPath,
|
|
2861
|
+
composer,
|
|
2862
|
+
globalDb,
|
|
2863
|
+
options = { resolveFirstMessage: true }
|
|
2864
|
+
}) {
|
|
2807
2865
|
if (!isNonEmptyString(composer.composerId)) {
|
|
2808
2866
|
return null;
|
|
2809
2867
|
}
|
|
@@ -2813,7 +2871,7 @@ function createComposerSummary(projectPath, workspaceDbPath, composer, globalDb)
|
|
|
2813
2871
|
}
|
|
2814
2872
|
const { composerId } = composer;
|
|
2815
2873
|
const unifiedMode = composer.unifiedMode ?? composer.forceMode ?? "chat";
|
|
2816
|
-
const firstMessage = resolveComposerFirstMessage(globalDb, composer, composerId);
|
|
2874
|
+
const firstMessage = options.resolveFirstMessage ? resolveComposerFirstMessage(globalDb, composer, composerId) : resolveComposerFallbackMessage(composer);
|
|
2817
2875
|
return {
|
|
2818
2876
|
kind: "composer",
|
|
2819
2877
|
rawSessionId: `composer:${composerId}`,
|
|
@@ -2840,6 +2898,31 @@ function parseProjectPathFromWorkspaceJson(content) {
|
|
|
2840
2898
|
}
|
|
2841
2899
|
return fileUrlToPath(parsed.folder);
|
|
2842
2900
|
}
|
|
2901
|
+
function discoverWorkspaceDescriptors(targetProjectPath) {
|
|
2902
|
+
return Effect21.gen(function* () {
|
|
2903
|
+
const workspaceStorageDir = yield* getCursorWorkspaceStorageDirEffect();
|
|
2904
|
+
const workspaceEntries = yield* readDirEntriesSafe2(workspaceStorageDir);
|
|
2905
|
+
const workspaceDescriptors = [];
|
|
2906
|
+
for (const entry of workspaceEntries) {
|
|
2907
|
+
if (!entry.isDirectory) {
|
|
2908
|
+
continue;
|
|
2909
|
+
}
|
|
2910
|
+
const workspaceDir = join12(workspaceStorageDir, entry.name);
|
|
2911
|
+
const workspaceJsonPath = join12(workspaceDir, "workspace.json");
|
|
2912
|
+
const workspaceDbPath = join12(workspaceDir, "state.vscdb");
|
|
2913
|
+
const exists = yield* fileExists3(workspaceJsonPath);
|
|
2914
|
+
if (!exists) {
|
|
2915
|
+
continue;
|
|
2916
|
+
}
|
|
2917
|
+
const projectPath = yield* readFileText3(workspaceJsonPath).pipe(Effect21.map(parseProjectPathFromWorkspaceJson), Effect21.catchAll(() => Effect21.succeed(null)));
|
|
2918
|
+
if (!projectPath || targetProjectPath && projectPath !== targetProjectPath) {
|
|
2919
|
+
continue;
|
|
2920
|
+
}
|
|
2921
|
+
workspaceDescriptors.push({ projectPath, workspaceDbPath });
|
|
2922
|
+
}
|
|
2923
|
+
return workspaceDescriptors;
|
|
2924
|
+
});
|
|
2925
|
+
}
|
|
2843
2926
|
function findProjectSessions(sessionsByProject, projectPath) {
|
|
2844
2927
|
const existing = sessionsByProject.get(projectPath);
|
|
2845
2928
|
if (existing) {
|
|
@@ -2852,6 +2935,46 @@ function findProjectSessions(sessionsByProject, projectPath) {
|
|
|
2852
2935
|
function pushSession(sessionsByProject, session) {
|
|
2853
2936
|
findProjectSessions(sessionsByProject, session.projectPath).push(session);
|
|
2854
2937
|
}
|
|
2938
|
+
function pushSessions(sessionsByProject, sessions) {
|
|
2939
|
+
for (const session of sessions) {
|
|
2940
|
+
pushSession(sessionsByProject, session);
|
|
2941
|
+
}
|
|
2942
|
+
}
|
|
2943
|
+
function collectComposerSessions({
|
|
2944
|
+
workspaceDescriptors,
|
|
2945
|
+
globalDb,
|
|
2946
|
+
sessionsByProject,
|
|
2947
|
+
composersById,
|
|
2948
|
+
options
|
|
2949
|
+
}) {
|
|
2950
|
+
return Effect21.gen(function* () {
|
|
2951
|
+
for (const descriptor of workspaceDescriptors) {
|
|
2952
|
+
const workspaceDb = yield* openCursorDbIfExists(descriptor.workspaceDbPath);
|
|
2953
|
+
if (!workspaceDb) {
|
|
2954
|
+
continue;
|
|
2955
|
+
}
|
|
2956
|
+
try {
|
|
2957
|
+
const composers = readWorkspaceComposerEntries(workspaceDb);
|
|
2958
|
+
for (const composer of composers) {
|
|
2959
|
+
const summary = createComposerSummary({
|
|
2960
|
+
projectPath: descriptor.projectPath,
|
|
2961
|
+
workspaceDbPath: descriptor.workspaceDbPath,
|
|
2962
|
+
composer,
|
|
2963
|
+
globalDb,
|
|
2964
|
+
options
|
|
2965
|
+
});
|
|
2966
|
+
if (!summary) {
|
|
2967
|
+
continue;
|
|
2968
|
+
}
|
|
2969
|
+
composersById.set(summary.composerId, summary);
|
|
2970
|
+
pushSession(sessionsByProject, summary);
|
|
2971
|
+
}
|
|
2972
|
+
} finally {
|
|
2973
|
+
workspaceDb.close();
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2976
|
+
});
|
|
2977
|
+
}
|
|
2855
2978
|
function readFirstTranscriptUserMessage(text) {
|
|
2856
2979
|
let firstMessage = "";
|
|
2857
2980
|
iterateJsonl3(text, ({ parsed }) => {
|
|
@@ -2895,45 +3018,50 @@ function listTranscriptFiles(agentTranscriptsDir) {
|
|
|
2895
3018
|
return files;
|
|
2896
3019
|
});
|
|
2897
3020
|
}
|
|
2898
|
-
function
|
|
3021
|
+
function createBackgroundAgentSummary(projectPath, transcript, firstMessage) {
|
|
3022
|
+
return {
|
|
3023
|
+
kind: "agent",
|
|
3024
|
+
rawSessionId: `agent:${transcript.agentId}`,
|
|
3025
|
+
projectPath,
|
|
3026
|
+
agentId: transcript.agentId,
|
|
3027
|
+
filePath: transcript.filePath,
|
|
3028
|
+
timestamp: transcript.mtimeIso,
|
|
3029
|
+
timestampMs: new Date(transcript.mtimeIso).getTime(),
|
|
3030
|
+
firstMessage,
|
|
3031
|
+
slug: transcript.agentId,
|
|
3032
|
+
model: "unknown",
|
|
3033
|
+
gitBranch: "",
|
|
3034
|
+
sessionType: "implementation"
|
|
3035
|
+
};
|
|
3036
|
+
}
|
|
3037
|
+
function discoverBackgroundAgentsForProject(projectPath, options = { readPreviewText: true }) {
|
|
2899
3038
|
return Effect21.gen(function* () {
|
|
2900
3039
|
const config = yield* PluginConfig;
|
|
2901
|
-
const
|
|
2902
|
-
const entries = yield* readDirEntriesSafe2(projectsDir);
|
|
2903
|
-
const encodedProjectPaths = new Map(workspaceProjectPaths.map((projectPath) => [encodeCursorProjectPath(projectPath), projectPath]));
|
|
3040
|
+
const agentTranscriptsDir = join12(config.dataDir, "projects", encodeCursorProjectPath(projectPath), "agent-transcripts");
|
|
2904
3041
|
const agentsById = new Map;
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
const agentTranscriptsDir = join12(projectsDir, entry.name, "agent-transcripts");
|
|
2914
|
-
const transcriptDirExists = yield* fileExists3(agentTranscriptsDir);
|
|
2915
|
-
if (!transcriptDirExists) {
|
|
2916
|
-
continue;
|
|
2917
|
-
}
|
|
2918
|
-
const transcripts = yield* listTranscriptFiles(agentTranscriptsDir);
|
|
2919
|
-
for (const transcript of transcripts) {
|
|
3042
|
+
const transcriptDirExists = yield* fileExists3(agentTranscriptsDir);
|
|
3043
|
+
if (!transcriptDirExists) {
|
|
3044
|
+
return agentsById;
|
|
3045
|
+
}
|
|
3046
|
+
const transcripts = yield* listTranscriptFiles(agentTranscriptsDir);
|
|
3047
|
+
for (const transcript of transcripts) {
|
|
3048
|
+
let firstMessage = "Cursor background agent";
|
|
3049
|
+
if (options.readPreviewText) {
|
|
2920
3050
|
const text = yield* readFileText3(transcript.filePath).pipe(Effect21.catchAll(() => Effect21.succeed("")));
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
sessionType: "implementation"
|
|
2936
|
-
});
|
|
3051
|
+
firstMessage = readFirstTranscriptUserMessage(text) || firstMessage;
|
|
3052
|
+
}
|
|
3053
|
+
agentsById.set(transcript.agentId, createBackgroundAgentSummary(projectPath, transcript, firstMessage));
|
|
3054
|
+
}
|
|
3055
|
+
return agentsById;
|
|
3056
|
+
});
|
|
3057
|
+
}
|
|
3058
|
+
function discoverBackgroundAgents(projectPaths, options = { readPreviewText: true }) {
|
|
3059
|
+
return Effect21.gen(function* () {
|
|
3060
|
+
const agentsById = new Map;
|
|
3061
|
+
for (const projectPath of new Set(projectPaths)) {
|
|
3062
|
+
const projectAgents = yield* discoverBackgroundAgentsForProject(projectPath, options);
|
|
3063
|
+
for (const agent of projectAgents.values()) {
|
|
3064
|
+
agentsById.set(agent.agentId, agent);
|
|
2937
3065
|
}
|
|
2938
3066
|
}
|
|
2939
3067
|
return agentsById;
|
|
@@ -2970,7 +3098,7 @@ function createPlanSummary(entry, projectPath, filePath, displayName) {
|
|
|
2970
3098
|
sessionType: "plan"
|
|
2971
3099
|
};
|
|
2972
3100
|
}
|
|
2973
|
-
function discoverMappedPlans(globalDb, composersById, agentsById) {
|
|
3101
|
+
function discoverMappedPlans(globalDb, composersById, agentsById, options = { loadDisplayName: true }) {
|
|
2974
3102
|
return Effect21.gen(function* () {
|
|
2975
3103
|
const plansById = new Map;
|
|
2976
3104
|
if (!globalDb) {
|
|
@@ -2992,8 +3120,8 @@ function discoverMappedPlans(globalDb, composersById, agentsById) {
|
|
|
2992
3120
|
if (!exists) {
|
|
2993
3121
|
continue;
|
|
2994
3122
|
}
|
|
2995
|
-
const displayName = yield* readPlanDisplayName(filePath).pipe(Effect21.catchAll(() => Effect21.succeed("")));
|
|
2996
|
-
const summary = createPlanSummary(entry, projectPath, filePath, displayName || entry.name?.trim() || "");
|
|
3123
|
+
const displayName = options.loadDisplayName ? yield* readPlanDisplayName(filePath).pipe(Effect21.catchAll(() => Effect21.succeed(""))) : "";
|
|
3124
|
+
const summary = createPlanSummary(entry, projectPath, filePath, displayName || entry.name?.trim() || "Cursor plan");
|
|
2997
3125
|
if (!summary) {
|
|
2998
3126
|
continue;
|
|
2999
3127
|
}
|
|
@@ -3024,63 +3152,57 @@ function buildProjects(sessionsByProject) {
|
|
|
3024
3152
|
sortByIsoDesc(projects, (project) => project.lastActivity);
|
|
3025
3153
|
return projects;
|
|
3026
3154
|
}
|
|
3155
|
+
function buildCursorProjectIndex(nativeId) {
|
|
3156
|
+
return Effect21.gen(function* () {
|
|
3157
|
+
const globalDb = yield* openCursorGlobalDb();
|
|
3158
|
+
const sessionsByProject = new Map;
|
|
3159
|
+
const composersById = new Map;
|
|
3160
|
+
try {
|
|
3161
|
+
const workspaceDescriptors = yield* discoverWorkspaceDescriptors(nativeId);
|
|
3162
|
+
yield* collectComposerSessions({
|
|
3163
|
+
workspaceDescriptors,
|
|
3164
|
+
globalDb,
|
|
3165
|
+
sessionsByProject,
|
|
3166
|
+
composersById,
|
|
3167
|
+
options: { resolveFirstMessage: true }
|
|
3168
|
+
});
|
|
3169
|
+
const agentsById = yield* discoverBackgroundAgents([nativeId], { readPreviewText: true });
|
|
3170
|
+
pushSessions(sessionsByProject, agentsById.values());
|
|
3171
|
+
const plansById = yield* discoverMappedPlans(globalDb, composersById, agentsById, { loadDisplayName: true });
|
|
3172
|
+
pushSessions(sessionsByProject, plansById.values());
|
|
3173
|
+
return {
|
|
3174
|
+
projects: buildProjects(sessionsByProject),
|
|
3175
|
+
sessionsByProject,
|
|
3176
|
+
composersById,
|
|
3177
|
+
agentsById,
|
|
3178
|
+
plansById
|
|
3179
|
+
};
|
|
3180
|
+
} finally {
|
|
3181
|
+
globalDb?.close();
|
|
3182
|
+
}
|
|
3183
|
+
});
|
|
3184
|
+
}
|
|
3027
3185
|
function buildCursorIndex() {
|
|
3028
3186
|
return Effect21.gen(function* () {
|
|
3029
|
-
const workspaceStorageDir = yield* getCursorWorkspaceStorageDirEffect();
|
|
3030
|
-
const workspaceEntries = yield* readDirEntriesSafe2(workspaceStorageDir);
|
|
3031
3187
|
const globalDb = yield* openCursorGlobalDb();
|
|
3032
3188
|
const sessionsByProject = new Map;
|
|
3033
3189
|
const composersById = new Map;
|
|
3034
3190
|
try {
|
|
3035
|
-
const workspaceDescriptors =
|
|
3036
|
-
for (const entry of workspaceEntries) {
|
|
3037
|
-
if (!entry.isDirectory) {
|
|
3038
|
-
continue;
|
|
3039
|
-
}
|
|
3040
|
-
const workspaceDir = join12(workspaceStorageDir, entry.name);
|
|
3041
|
-
const workspaceJsonPath = join12(workspaceDir, "workspace.json");
|
|
3042
|
-
const workspaceDbPath = join12(workspaceDir, "state.vscdb");
|
|
3043
|
-
const exists = yield* fileExists3(workspaceJsonPath);
|
|
3044
|
-
if (!exists) {
|
|
3045
|
-
continue;
|
|
3046
|
-
}
|
|
3047
|
-
const projectPath = yield* readFileText3(workspaceJsonPath).pipe(Effect21.map(parseProjectPathFromWorkspaceJson), Effect21.catchAll(() => Effect21.succeed(null)));
|
|
3048
|
-
if (!projectPath) {
|
|
3049
|
-
continue;
|
|
3050
|
-
}
|
|
3051
|
-
workspaceDescriptors.push({ projectPath, workspaceDbPath });
|
|
3052
|
-
}
|
|
3191
|
+
const workspaceDescriptors = yield* discoverWorkspaceDescriptors();
|
|
3053
3192
|
const workspaceProjectPaths = [...new Set(workspaceDescriptors.map((descriptor) => descriptor.projectPath))];
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
for (const composer of composers) {
|
|
3062
|
-
const summary = createComposerSummary(descriptor.projectPath, descriptor.workspaceDbPath, composer, globalDb);
|
|
3063
|
-
if (!summary) {
|
|
3064
|
-
continue;
|
|
3065
|
-
}
|
|
3066
|
-
composersById.set(summary.composerId, summary);
|
|
3067
|
-
pushSession(sessionsByProject, summary);
|
|
3068
|
-
}
|
|
3069
|
-
} finally {
|
|
3070
|
-
workspaceDb.close();
|
|
3071
|
-
}
|
|
3072
|
-
}
|
|
3193
|
+
yield* collectComposerSessions({
|
|
3194
|
+
workspaceDescriptors,
|
|
3195
|
+
globalDb,
|
|
3196
|
+
sessionsByProject,
|
|
3197
|
+
composersById,
|
|
3198
|
+
options: { resolveFirstMessage: true }
|
|
3199
|
+
});
|
|
3073
3200
|
const agentsById = yield* discoverBackgroundAgents(workspaceProjectPaths);
|
|
3074
|
-
|
|
3075
|
-
pushSession(sessionsByProject, agent);
|
|
3076
|
-
}
|
|
3201
|
+
pushSessions(sessionsByProject, agentsById.values());
|
|
3077
3202
|
const plansById = yield* discoverMappedPlans(globalDb, composersById, agentsById);
|
|
3078
|
-
|
|
3079
|
-
pushSession(sessionsByProject, plan);
|
|
3080
|
-
}
|
|
3081
|
-
const projects = buildProjects(sessionsByProject);
|
|
3203
|
+
pushSessions(sessionsByProject, plansById.values());
|
|
3082
3204
|
return {
|
|
3083
|
-
projects,
|
|
3205
|
+
projects: buildProjects(sessionsByProject),
|
|
3084
3206
|
sessionsByProject,
|
|
3085
3207
|
composersById,
|
|
3086
3208
|
agentsById,
|
|
@@ -3103,11 +3225,44 @@ function toSessionSummary(session) {
|
|
|
3103
3225
|
sessionType: session.sessionType
|
|
3104
3226
|
};
|
|
3105
3227
|
}
|
|
3228
|
+
function buildCursorDiscoveryIndex() {
|
|
3229
|
+
return buildCursorIndex().pipe(Effect21.map((index) => ({
|
|
3230
|
+
projects: index.projects,
|
|
3231
|
+
sessionsByNativeId: new Map([...index.sessionsByProject.entries()].map(([projectPath, sessions]) => [
|
|
3232
|
+
projectPath,
|
|
3233
|
+
sessions.map(toSessionSummary)
|
|
3234
|
+
]))
|
|
3235
|
+
})));
|
|
3236
|
+
}
|
|
3106
3237
|
function discoverCursorProjects() {
|
|
3107
|
-
return
|
|
3238
|
+
return Effect21.gen(function* () {
|
|
3239
|
+
const globalDb = yield* openCursorGlobalDb();
|
|
3240
|
+
const sessionsByProject = new Map;
|
|
3241
|
+
const composersById = new Map;
|
|
3242
|
+
try {
|
|
3243
|
+
const workspaceDescriptors = yield* discoverWorkspaceDescriptors();
|
|
3244
|
+
const workspaceProjectPaths = [...new Set(workspaceDescriptors.map((descriptor) => descriptor.projectPath))];
|
|
3245
|
+
yield* collectComposerSessions({
|
|
3246
|
+
workspaceDescriptors,
|
|
3247
|
+
globalDb,
|
|
3248
|
+
sessionsByProject,
|
|
3249
|
+
composersById,
|
|
3250
|
+
options: { resolveFirstMessage: false }
|
|
3251
|
+
});
|
|
3252
|
+
const agentsById = yield* discoverBackgroundAgents(workspaceProjectPaths, { readPreviewText: false });
|
|
3253
|
+
pushSessions(sessionsByProject, agentsById.values());
|
|
3254
|
+
const plansById = yield* discoverMappedPlans(globalDb, composersById, agentsById, {
|
|
3255
|
+
loadDisplayName: false
|
|
3256
|
+
});
|
|
3257
|
+
pushSessions(sessionsByProject, plansById.values());
|
|
3258
|
+
return buildProjects(sessionsByProject);
|
|
3259
|
+
} finally {
|
|
3260
|
+
globalDb?.close();
|
|
3261
|
+
}
|
|
3262
|
+
});
|
|
3108
3263
|
}
|
|
3109
3264
|
function listCursorSessions(nativeId) {
|
|
3110
|
-
return
|
|
3265
|
+
return buildCursorProjectIndex(nativeId).pipe(Effect21.map((index) => {
|
|
3111
3266
|
const sessions = index.sessionsByProject.get(nativeId) ?? [];
|
|
3112
3267
|
sortByIsoDesc(sessions, (session) => session.timestamp);
|
|
3113
3268
|
return sessions.map(toSessionSummary);
|
|
@@ -3341,7 +3496,7 @@ function loadCursorAgentSession(summary) {
|
|
|
3341
3496
|
}
|
|
3342
3497
|
function loadCursorSession(nativeId, sessionId) {
|
|
3343
3498
|
return Effect22.gen(function* () {
|
|
3344
|
-
const index = yield*
|
|
3499
|
+
const index = yield* buildCursorProjectIndex(nativeId);
|
|
3345
3500
|
const session = index.composersById.get(sessionId.replace(COMPOSER_PREFIX_REGEX, "")) ?? index.agentsById.get(sessionId.replace(AGENT_PREFIX_REGEX, "")) ?? index.plansById.get(sessionId.replace(PLAN_PREFIX_REGEX, ""));
|
|
3346
3501
|
if (!session || session.projectPath !== nativeId) {
|
|
3347
3502
|
return yield* Effect22.fail(new Error(`Cursor session not found: ${sessionId}`));
|
|
@@ -3379,6 +3534,12 @@ var cursorPlugin = {
|
|
|
3379
3534
|
message: String(err),
|
|
3380
3535
|
cause: err
|
|
3381
3536
|
})))),
|
|
3537
|
+
discoverIndex: buildCursorDiscoveryIndex().pipe(Effect23.catchAll((err) => Effect23.fail(new PluginError({
|
|
3538
|
+
pluginId: "cursor",
|
|
3539
|
+
operation: "discoverIndex",
|
|
3540
|
+
message: String(err),
|
|
3541
|
+
cause: err
|
|
3542
|
+
})))),
|
|
3382
3543
|
listSessions: (nativeId) => listCursorSessions(nativeId).pipe(Effect23.catchAll((err) => Effect23.fail(new PluginError({
|
|
3383
3544
|
pluginId: "cursor",
|
|
3384
3545
|
operation: "listSessions",
|
|
@@ -3569,13 +3730,11 @@ function getProjects(registry) {
|
|
|
3569
3730
|
}
|
|
3570
3731
|
function getSessions(registry, params) {
|
|
3571
3732
|
return Effect27.gen(function* () {
|
|
3572
|
-
const
|
|
3573
|
-
|
|
3574
|
-
if (!project) {
|
|
3733
|
+
const discovered = yield* registry.discoverAllProjectsWithSessions();
|
|
3734
|
+
if (!discovered.projects.find((project) => project.encodedPath === params.encodedPath)) {
|
|
3575
3735
|
return { sessions: [] };
|
|
3576
3736
|
}
|
|
3577
|
-
|
|
3578
|
-
return { sessions };
|
|
3737
|
+
return { sessions: discovered.sessionsByEncodedPath.get(params.encodedPath) ?? [] };
|
|
3579
3738
|
});
|
|
3580
3739
|
}
|
|
3581
3740
|
function getSession(registry, params) {
|
|
@@ -3633,10 +3792,10 @@ function projectNameFromPath(fullPath) {
|
|
|
3633
3792
|
}
|
|
3634
3793
|
function searchSessions(registry) {
|
|
3635
3794
|
return Effect27.gen(function* () {
|
|
3636
|
-
const
|
|
3637
|
-
const perProject = yield* Effect27.forEach(projects, (project) => registry.listAllSessions(project).pipe(Effect27.map((sessions) => ({ project, sessions }))), { concurrency: "unbounded" });
|
|
3795
|
+
const discovered = yield* registry.discoverAllProjectsWithSessions();
|
|
3638
3796
|
const allSessions = [];
|
|
3639
|
-
for (const
|
|
3797
|
+
for (const project of discovered.projects) {
|
|
3798
|
+
const sessions = discovered.sessionsByEncodedPath.get(project.encodedPath) ?? [];
|
|
3640
3799
|
const projectName = projectNameFromPath(project.name);
|
|
3641
3800
|
for (const session of sessions) {
|
|
3642
3801
|
allSessions.push({
|
|
@@ -3755,6 +3914,8 @@ function updateUpdateSettings(settingsPath, params) {
|
|
|
3755
3914
|
}
|
|
3756
3915
|
|
|
3757
3916
|
// ../../packages/server/src/services/stats-service.ts
|
|
3917
|
+
import { dirname as dirname2, join as join14 } from "node:path";
|
|
3918
|
+
import { FileSystem as FileSystem11 } from "@effect/platform";
|
|
3758
3919
|
import { Effect as Effect30 } from "effect";
|
|
3759
3920
|
|
|
3760
3921
|
// ../../packages/server/src/services/stats.ts
|
|
@@ -3820,11 +3981,11 @@ function countVisibleMessages(turns) {
|
|
|
3820
3981
|
}
|
|
3821
3982
|
function collectSessionsWithProjects(registry, stats) {
|
|
3822
3983
|
return Effect29.gen(function* () {
|
|
3823
|
-
const
|
|
3824
|
-
stats.projects = projects.length;
|
|
3984
|
+
const discovered = yield* registry.discoverAllProjectsWithSessions();
|
|
3985
|
+
stats.projects = discovered.projects.length;
|
|
3825
3986
|
const sessionsWithProject = [];
|
|
3826
|
-
for (const project of projects) {
|
|
3827
|
-
const sessions =
|
|
3987
|
+
for (const project of discovered.projects) {
|
|
3988
|
+
const sessions = discovered.sessionsByEncodedPath.get(project.encodedPath) ?? [];
|
|
3828
3989
|
stats.sessions += sessions.length;
|
|
3829
3990
|
for (const session of sessions) {
|
|
3830
3991
|
sessionsWithProject.push({ project, session });
|
|
@@ -3865,6 +4026,9 @@ function applyUsageStats(stats, modelUsage, usage) {
|
|
|
3865
4026
|
modelUsage.cacheReadTokens += usage.cacheReadTokens ?? 0;
|
|
3866
4027
|
modelUsage.cacheCreationTokens += usage.cacheCreationTokens ?? 0;
|
|
3867
4028
|
}
|
|
4029
|
+
function totalUsageTokens(usage) {
|
|
4030
|
+
return usage.inputTokens + usage.outputTokens + (usage.cacheReadTokens ?? 0) + (usage.cacheCreationTokens ?? 0);
|
|
4031
|
+
}
|
|
3868
4032
|
function applyTurnStats(stats, turns, fallbackModel) {
|
|
3869
4033
|
stats.messages += countVisibleMessages(turns);
|
|
3870
4034
|
for (const turn of turns) {
|
|
@@ -3872,10 +4036,10 @@ function applyTurnStats(stats, turns, fallbackModel) {
|
|
|
3872
4036
|
continue;
|
|
3873
4037
|
}
|
|
3874
4038
|
stats.toolCalls += turn.contentBlocks.filter((block) => block.type === "tool_call").length;
|
|
3875
|
-
|
|
3876
|
-
if (!turn.usage) {
|
|
4039
|
+
if (!turn.usage || totalUsageTokens(turn.usage) <= 0) {
|
|
3877
4040
|
continue;
|
|
3878
4041
|
}
|
|
4042
|
+
const modelUsage = ensureModelUsage(stats.models, turn.model || fallbackModel || "unknown");
|
|
3879
4043
|
applyUsageStats(stats, modelUsage, turn.usage);
|
|
3880
4044
|
}
|
|
3881
4045
|
}
|
|
@@ -3899,10 +4063,163 @@ function scanStats(registry) {
|
|
|
3899
4063
|
}
|
|
3900
4064
|
|
|
3901
4065
|
// ../../packages/server/src/services/stats-service.ts
|
|
3902
|
-
|
|
4066
|
+
var STATS_CACHE_FILENAME = "stats-cache.json";
|
|
4067
|
+
var refreshBootTimes = new Map;
|
|
4068
|
+
var refreshEpochs = new Map;
|
|
4069
|
+
var refreshingCachePaths = new Set;
|
|
4070
|
+
function getStatsCachePath(settingsPath) {
|
|
4071
|
+
return join14(dirname2(settingsPath), STATS_CACHE_FILENAME);
|
|
4072
|
+
}
|
|
4073
|
+
function getRefreshBootTime(cachePath) {
|
|
4074
|
+
const existing = refreshBootTimes.get(cachePath);
|
|
4075
|
+
if (existing) {
|
|
4076
|
+
return existing;
|
|
4077
|
+
}
|
|
4078
|
+
const bootTime = new Date().toISOString();
|
|
4079
|
+
refreshBootTimes.set(cachePath, bootTime);
|
|
4080
|
+
return bootTime;
|
|
4081
|
+
}
|
|
4082
|
+
function getRefreshEpoch(cachePath) {
|
|
4083
|
+
return refreshEpochs.get(cachePath) ?? 0;
|
|
4084
|
+
}
|
|
4085
|
+
function bumpRefreshEpoch(cachePath) {
|
|
4086
|
+
const nextEpoch = getRefreshEpoch(cachePath) + 1;
|
|
4087
|
+
refreshEpochs.set(cachePath, nextEpoch);
|
|
4088
|
+
return nextEpoch;
|
|
4089
|
+
}
|
|
4090
|
+
function isDashboardStats(value) {
|
|
4091
|
+
if (typeof value !== "object" || value === null) {
|
|
4092
|
+
return false;
|
|
4093
|
+
}
|
|
4094
|
+
const candidate = value;
|
|
4095
|
+
const requiredNumberFields = [
|
|
4096
|
+
"projects",
|
|
4097
|
+
"sessions",
|
|
4098
|
+
"messages",
|
|
4099
|
+
"todaySessions",
|
|
4100
|
+
"thisWeekSessions",
|
|
4101
|
+
"inputTokens",
|
|
4102
|
+
"outputTokens",
|
|
4103
|
+
"cacheReadTokens",
|
|
4104
|
+
"cacheCreationTokens",
|
|
4105
|
+
"toolCalls"
|
|
4106
|
+
];
|
|
4107
|
+
for (const field of requiredNumberFields) {
|
|
4108
|
+
if (typeof candidate[field] !== "number") {
|
|
4109
|
+
return false;
|
|
4110
|
+
}
|
|
4111
|
+
}
|
|
4112
|
+
return typeof candidate["models"] === "object" && candidate["models"] !== null;
|
|
4113
|
+
}
|
|
4114
|
+
function isStatsCacheFile(value) {
|
|
4115
|
+
if (typeof value !== "object" || value === null) {
|
|
4116
|
+
return false;
|
|
4117
|
+
}
|
|
4118
|
+
const candidate = value;
|
|
4119
|
+
return candidate["version"] === 1 && typeof candidate["cachedAt"] === "string" && isDashboardStats(candidate["stats"]);
|
|
4120
|
+
}
|
|
4121
|
+
function loadStatsCache(settingsPath) {
|
|
4122
|
+
return Effect30.gen(function* () {
|
|
4123
|
+
const fs = yield* FileSystem11.FileSystem;
|
|
4124
|
+
const cachePath = getStatsCachePath(settingsPath);
|
|
4125
|
+
const raw = yield* fs.readFileString(cachePath).pipe(Effect30.catchAll(() => Effect30.succeed(null)));
|
|
4126
|
+
if (raw === null) {
|
|
4127
|
+
return null;
|
|
4128
|
+
}
|
|
4129
|
+
const parsed = yield* Effect30.try({
|
|
4130
|
+
try: () => JSON.parse(raw),
|
|
4131
|
+
catch: () => null
|
|
4132
|
+
}).pipe(Effect30.catchAll(() => Effect30.succeed(null)));
|
|
4133
|
+
return parsed !== null && isStatsCacheFile(parsed) ? parsed : null;
|
|
4134
|
+
});
|
|
4135
|
+
}
|
|
4136
|
+
function persistStatsCache(settingsPath, stats, expectedEpoch) {
|
|
4137
|
+
return Effect30.gen(function* () {
|
|
4138
|
+
const fs = yield* FileSystem11.FileSystem;
|
|
4139
|
+
const cachePath = getStatsCachePath(settingsPath);
|
|
4140
|
+
const cacheDir = dirname2(cachePath);
|
|
4141
|
+
const cachedAt = new Date().toISOString();
|
|
4142
|
+
yield* fs.makeDirectory(cacheDir, { recursive: true }).pipe(Effect30.catchAll(() => Effect30.void));
|
|
4143
|
+
if (getRefreshEpoch(cachePath) !== expectedEpoch) {
|
|
4144
|
+
return null;
|
|
4145
|
+
}
|
|
4146
|
+
const tmpPath = join14(cacheDir, `.stats-cache-${Date.now()}.tmp`);
|
|
4147
|
+
const payload = {
|
|
4148
|
+
version: 1,
|
|
4149
|
+
cachedAt,
|
|
4150
|
+
stats
|
|
4151
|
+
};
|
|
4152
|
+
const wroteTempFile = yield* fs.writeFileString(tmpPath, JSON.stringify(payload, null, 2)).pipe(Effect30.map(() => true), Effect30.catchAll(() => Effect30.succeed(false)));
|
|
4153
|
+
if (!wroteTempFile) {
|
|
4154
|
+
return null;
|
|
4155
|
+
}
|
|
4156
|
+
if (getRefreshEpoch(cachePath) !== expectedEpoch) {
|
|
4157
|
+
yield* fs.remove(tmpPath).pipe(Effect30.catchAll(() => Effect30.void));
|
|
4158
|
+
return null;
|
|
4159
|
+
}
|
|
4160
|
+
const renamed = yield* fs.rename(tmpPath, cachePath).pipe(Effect30.map(() => true), Effect30.catchAll(() => fs.remove(tmpPath).pipe(Effect30.catchAll(() => Effect30.void), Effect30.map(() => false))));
|
|
4161
|
+
if (!renamed) {
|
|
4162
|
+
return null;
|
|
4163
|
+
}
|
|
4164
|
+
return getRefreshEpoch(cachePath) === expectedEpoch ? cachedAt : null;
|
|
4165
|
+
});
|
|
4166
|
+
}
|
|
4167
|
+
function computeAndPersistStats(settingsPath, registry, expectedEpoch) {
|
|
3903
4168
|
return Effect30.gen(function* () {
|
|
3904
4169
|
const stats = yield* scanStats(registry);
|
|
3905
|
-
|
|
4170
|
+
const cachedAt = yield* persistStatsCache(settingsPath, stats, expectedEpoch);
|
|
4171
|
+
return { stats, ...cachedAt ? { cachedAt } : {} };
|
|
4172
|
+
});
|
|
4173
|
+
}
|
|
4174
|
+
function refreshStats(settingsPath, registry) {
|
|
4175
|
+
return Effect30.gen(function* () {
|
|
4176
|
+
const cachePath = getStatsCachePath(settingsPath);
|
|
4177
|
+
const expectedEpoch = getRefreshEpoch(cachePath);
|
|
4178
|
+
refreshingCachePaths.add(cachePath);
|
|
4179
|
+
const result = yield* computeAndPersistStats(settingsPath, registry, expectedEpoch);
|
|
4180
|
+
return { ...result, refreshing: false };
|
|
4181
|
+
}).pipe(Effect30.ensuring(Effect30.sync(() => {
|
|
4182
|
+
refreshingCachePaths.delete(getStatsCachePath(settingsPath));
|
|
4183
|
+
})));
|
|
4184
|
+
}
|
|
4185
|
+
function scheduleRefresh(settingsPath, registry) {
|
|
4186
|
+
return Effect30.gen(function* () {
|
|
4187
|
+
const cachePath = getStatsCachePath(settingsPath);
|
|
4188
|
+
if (refreshingCachePaths.has(cachePath)) {
|
|
4189
|
+
return;
|
|
4190
|
+
}
|
|
4191
|
+
const expectedEpoch = getRefreshEpoch(cachePath);
|
|
4192
|
+
refreshingCachePaths.add(cachePath);
|
|
4193
|
+
yield* computeAndPersistStats(settingsPath, registry, expectedEpoch).pipe(Effect30.catchAllCause(() => Effect30.void), Effect30.ensuring(Effect30.sync(() => {
|
|
4194
|
+
refreshingCachePaths.delete(cachePath);
|
|
4195
|
+
})), Effect30.forkDaemon);
|
|
4196
|
+
});
|
|
4197
|
+
}
|
|
4198
|
+
function getStats(settingsPath, registry) {
|
|
4199
|
+
return Effect30.gen(function* () {
|
|
4200
|
+
const cachePath = getStatsCachePath(settingsPath);
|
|
4201
|
+
const cached = yield* loadStatsCache(settingsPath);
|
|
4202
|
+
if (!cached) {
|
|
4203
|
+
return yield* refreshStats(settingsPath, registry);
|
|
4204
|
+
}
|
|
4205
|
+
const bootTime = getRefreshBootTime(cachePath);
|
|
4206
|
+
const shouldRefresh = cached.cachedAt < bootTime;
|
|
4207
|
+
if (shouldRefresh) {
|
|
4208
|
+
yield* scheduleRefresh(settingsPath, registry);
|
|
4209
|
+
}
|
|
4210
|
+
return {
|
|
4211
|
+
stats: cached.stats,
|
|
4212
|
+
cachedAt: cached.cachedAt,
|
|
4213
|
+
refreshing: shouldRefresh || refreshingCachePaths.has(cachePath)
|
|
4214
|
+
};
|
|
4215
|
+
});
|
|
4216
|
+
}
|
|
4217
|
+
function invalidateStatsCache(settingsPath) {
|
|
4218
|
+
return Effect30.gen(function* () {
|
|
4219
|
+
const fs = yield* FileSystem11.FileSystem;
|
|
4220
|
+
const cachePath = getStatsCachePath(settingsPath);
|
|
4221
|
+
bumpRefreshEpoch(cachePath);
|
|
4222
|
+
yield* fs.remove(cachePath).pipe(Effect30.catchAll(() => Effect30.void));
|
|
3906
4223
|
});
|
|
3907
4224
|
}
|
|
3908
4225
|
|
|
@@ -3931,7 +4248,7 @@ var KloviServicesLive = Layer6.effect(KloviServices, Effect31.gen(function* () {
|
|
|
3931
4248
|
return {
|
|
3932
4249
|
acceptRisks: () => completeOnboarding(settingsPath),
|
|
3933
4250
|
getVersion: () => Effect31.succeed(getVersion(versionState)),
|
|
3934
|
-
getStats: () => getStats(registry),
|
|
4251
|
+
getStats: () => getStats(settingsPath, registry),
|
|
3935
4252
|
getProjects: () => getProjects(registry),
|
|
3936
4253
|
getSessions: (params) => getSessions(registry, params),
|
|
3937
4254
|
getSession: (params) => getSession(registry, params),
|
|
@@ -3941,6 +4258,7 @@ var KloviServicesLive = Layer6.effect(KloviServices, Effect31.gen(function* () {
|
|
|
3941
4258
|
updatePluginSetting: (params) => Effect31.gen(function* () {
|
|
3942
4259
|
const result = yield* updatePluginSetting(settingsPath, params);
|
|
3943
4260
|
yield* refreshRegistry();
|
|
4261
|
+
yield* invalidateStatsCache(settingsPath);
|
|
3944
4262
|
return result;
|
|
3945
4263
|
}),
|
|
3946
4264
|
getGeneralSettings: () => getGeneralSettings(settingsPath),
|
|
@@ -3949,6 +4267,7 @@ var KloviServicesLive = Layer6.effect(KloviServices, Effect31.gen(function* () {
|
|
|
3949
4267
|
resetSettings: () => Effect31.gen(function* () {
|
|
3950
4268
|
const result = yield* resetSettings(settingsPath);
|
|
3951
4269
|
yield* refreshRegistry();
|
|
4270
|
+
yield* invalidateStatsCache(settingsPath);
|
|
3952
4271
|
return result;
|
|
3953
4272
|
}),
|
|
3954
4273
|
getUpdateSettings: () => getUpdateSettings(settingsPath),
|
|
@@ -3961,7 +4280,7 @@ var KloviServicesLive = Layer6.effect(KloviServices, Effect31.gen(function* () {
|
|
|
3961
4280
|
// ../../packages/server/src/effect/bootstrap.ts
|
|
3962
4281
|
function getDefaultSettingsPath() {
|
|
3963
4282
|
const home = process.env["HOME"] ?? process.env["USERPROFILE"] ?? "";
|
|
3964
|
-
return
|
|
4283
|
+
return join15(home, ".klovi", "settings.json");
|
|
3965
4284
|
}
|
|
3966
4285
|
function detectRuntime(requested = "auto") {
|
|
3967
4286
|
if (requested !== "auto") {
|
|
@@ -4100,11 +4419,16 @@ import { Effect as Effect35 } from "effect";
|
|
|
4100
4419
|
// src/static-handler.ts
|
|
4101
4420
|
import { HttpServerRequest as HttpServerRequest2, HttpServerResponse as HttpServerResponse2 } from "@effect/platform";
|
|
4102
4421
|
import { Effect as Effect34 } from "effect";
|
|
4422
|
+
var notFound = HttpServerResponse2.unsafeJson({ error: "Not found" }, { status: 404 });
|
|
4423
|
+
var isNavigationRequest = (pathname) => {
|
|
4424
|
+
const lastSegment2 = pathname.split("/").pop() ?? "";
|
|
4425
|
+
return !lastSegment2.includes(".");
|
|
4426
|
+
};
|
|
4103
4427
|
var makeStaticHandler = (staticDir) => Effect34.gen(function* () {
|
|
4104
4428
|
const req = yield* HttpServerRequest2.HttpServerRequest;
|
|
4105
4429
|
const url = new URL(req.url, "http://localhost");
|
|
4106
4430
|
const filePath = url.pathname === "/" ? "/index.html" : url.pathname;
|
|
4107
|
-
return yield* HttpServerResponse2.file(`${staticDir}${filePath}`).pipe(Effect34.orElse(() => HttpServerResponse2.file(`${staticDir}/index.html`)
|
|
4431
|
+
return yield* HttpServerResponse2.file(`${staticDir}${filePath}`).pipe(Effect34.orElse(() => isNavigationRequest(url.pathname) ? HttpServerResponse2.file(`${staticDir}/index.html`).pipe(Effect34.orElse(() => Effect34.succeed(notFound))) : Effect34.succeed(notFound)));
|
|
4108
4432
|
});
|
|
4109
4433
|
|
|
4110
4434
|
// src/http-app.ts
|