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