getprismo 0.1.26 → 0.1.28
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 +62 -3
- package/lib/prismo-dev/cursor-sessions.js +641 -0
- package/lib/prismo-dev/firewall.js +119 -0
- package/lib/prismo-dev/mcp.js +16 -0
- package/lib/prismo-dev/scan-detect.js +30 -8
- package/lib/prismo-dev/scan.js +30 -1
- package/lib/prismo-dev/usage-sessions.js +68 -0
- package/lib/prismo-dev/usage-watch.js +23 -0
- package/lib/prismo-dev-scan.js +101 -12
- package/package.json +1 -1
|
@@ -99,6 +99,58 @@ function buildBlockedContext(ctx) {
|
|
|
99
99
|
return uniq(blocked).slice(0, 80);
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
function isGeneratedLike(value) {
|
|
103
|
+
const text = String(value || "").replace(/\\/g, "/").toLowerCase();
|
|
104
|
+
return /(^|\/)(node_modules|dist|build|coverage|\.next|__pycache__|logs?|events?|source-streams?|session-dumps?|playwright-report|test-results)(\/|$)/.test(text)
|
|
105
|
+
|| /(^|\/)(package-lock\.json|yarn\.lock|pnpm-lock\.yaml|bun\.lockb)$/.test(text)
|
|
106
|
+
|| /\.(log|jsonl|ndjson|map|pyc)$/.test(text);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function firewallPatternForPath(value) {
|
|
110
|
+
const text = String(value || "").replace(/\\/g, "/").replace(/\s+\(\d+x\)$/, "");
|
|
111
|
+
if (!text) return null;
|
|
112
|
+
if (/(^|\/)__pycache__(\/|$)/.test(text)) return "**/__pycache__/**";
|
|
113
|
+
if (/(^|\/)(node_modules|dist|build|coverage|\.next|logs?|events?|source-streams?|session-dumps?|playwright-report|test-results)\//.test(text)) {
|
|
114
|
+
const match = text.match(/(^|\/)(node_modules|dist|build|coverage|\.next|logs?|events?|source-streams?|session-dumps?|playwright-report|test-results)\//);
|
|
115
|
+
const prefix = text.slice(0, match.index + match[0].length).replace(/\/$/, "");
|
|
116
|
+
return `${prefix}/**`;
|
|
117
|
+
}
|
|
118
|
+
return text;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function timelineEventPath(event) {
|
|
122
|
+
const detail = String(event?.detail || "");
|
|
123
|
+
const match = detail.match(/^(.+?)\s+\(\d+x\)$/);
|
|
124
|
+
return (match ? match[1] : detail).trim();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function buildTimelineFirewallSuggestions(ctx, session, taskScope) {
|
|
128
|
+
const baseAllowed = buildAllowedContext(ctx, taskScope);
|
|
129
|
+
const baseBlocked = buildBlockedContext(ctx);
|
|
130
|
+
const timeline = session?.timeline || [];
|
|
131
|
+
const repeated = session?.repeatedPathMentions || [];
|
|
132
|
+
const artifacts = session?.generatedArtifacts || [];
|
|
133
|
+
|
|
134
|
+
const sessionAllowed = repeated
|
|
135
|
+
.map((item) => item.value)
|
|
136
|
+
.filter((value) => value && !isGeneratedLike(value))
|
|
137
|
+
.map(globForFile)
|
|
138
|
+
.slice(0, 20);
|
|
139
|
+
|
|
140
|
+
const sessionBlocked = [
|
|
141
|
+
...artifacts.map((item) => item.value),
|
|
142
|
+
...timeline.filter((event) => event.type === "artifact-leak").map(timelineEventPath),
|
|
143
|
+
...repeated.map((item) => item.value).filter(isGeneratedLike),
|
|
144
|
+
].map(firewallPatternForPath).filter(Boolean);
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
allowed: uniq([...sessionAllowed, ...baseAllowed]).slice(0, 80),
|
|
148
|
+
blocked: uniq([...sessionBlocked, ...baseBlocked]).slice(0, 100),
|
|
149
|
+
sessionAllowed: uniq(sessionAllowed),
|
|
150
|
+
sessionBlocked: uniq(sessionBlocked),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
102
154
|
function renderLines(title, items) {
|
|
103
155
|
return [`# ${title}`, "", ...items.map((item) => item)].join("\n") + "\n";
|
|
104
156
|
}
|
|
@@ -187,6 +239,71 @@ function runFirewall(rootDir = process.cwd(), options = {}) {
|
|
|
187
239
|
return result;
|
|
188
240
|
}
|
|
189
241
|
|
|
242
|
+
function renderTimelineFirewallSuggestions(result) {
|
|
243
|
+
const lines = [];
|
|
244
|
+
lines.push("# Prismo Timeline Firewall Suggestions");
|
|
245
|
+
lines.push("");
|
|
246
|
+
lines.push(`Generated: ${result.generatedAt}`);
|
|
247
|
+
lines.push(`Task: ${result.task}`);
|
|
248
|
+
lines.push(`Scope: ${result.scope}`);
|
|
249
|
+
if (result.sessionId) lines.push(`Session: ${result.sessionId}`);
|
|
250
|
+
lines.push("");
|
|
251
|
+
lines.push("These suggestions came from `cc timeline` session evidence. They are safe recommendation files; Prismo does not overwrite your active firewall unless you copy/apply them.");
|
|
252
|
+
lines.push("");
|
|
253
|
+
lines.push("## Session-Derived Allowed Context");
|
|
254
|
+
lines.push("");
|
|
255
|
+
if (result.sessionAllowed.length) result.sessionAllowed.forEach((item) => lines.push(`- ${item}`));
|
|
256
|
+
else lines.push("- No repeated source files were strong enough to promote.");
|
|
257
|
+
lines.push("");
|
|
258
|
+
lines.push("## Session-Derived Blocked Context");
|
|
259
|
+
lines.push("");
|
|
260
|
+
if (result.sessionBlocked.length) result.sessionBlocked.forEach((item) => lines.push(`- ${item}`));
|
|
261
|
+
else lines.push("- No generated/noisy paths were strong enough to add.");
|
|
262
|
+
lines.push("");
|
|
263
|
+
lines.push("## Next Session Prompt");
|
|
264
|
+
lines.push("");
|
|
265
|
+
lines.push("```text");
|
|
266
|
+
lines.push(`Use .prismo/context-firewall.suggested.md as the starting context policy for this ${result.task} task.`);
|
|
267
|
+
lines.push("Start from allowed context first. Do not read blocked paths unless you explain why they are required.");
|
|
268
|
+
lines.push("If you need wider context, name the exact file and reason before reading.");
|
|
269
|
+
lines.push("```");
|
|
270
|
+
lines.push("");
|
|
271
|
+
return lines.join("\n");
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function runTimelineFirewallSuggestions(rootDir = process.cwd(), session = null, options = {}) {
|
|
275
|
+
const ctx = createOptimizeContext(rootDir, options.scope || null);
|
|
276
|
+
const task = options.task || options.scope || "timeline-followup";
|
|
277
|
+
const scope = inferTaskScope(task, ctx);
|
|
278
|
+
const suggestions = buildTimelineFirewallSuggestions(ctx, session, scope);
|
|
279
|
+
const result = {
|
|
280
|
+
root: ctx.root,
|
|
281
|
+
task,
|
|
282
|
+
scope,
|
|
283
|
+
sessionId: session?.sessionId || null,
|
|
284
|
+
allowed: suggestions.allowed,
|
|
285
|
+
blocked: suggestions.blocked,
|
|
286
|
+
sessionAllowed: suggestions.sessionAllowed,
|
|
287
|
+
sessionBlocked: suggestions.sessionBlocked,
|
|
288
|
+
generatedAt: new Date().toISOString(),
|
|
289
|
+
generatedFiles: [
|
|
290
|
+
".prismo/timeline-firewall-suggestions.md",
|
|
291
|
+
".prismo/context-firewall.suggested.md",
|
|
292
|
+
".prismo/allowed-context.suggested.txt",
|
|
293
|
+
".prismo/blocked-context.suggested.txt",
|
|
294
|
+
],
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
if (!options.dryRun) {
|
|
298
|
+
writeGeneratedFile(ctx.root, ".prismo/timeline-firewall-suggestions.md", renderTimelineFirewallSuggestions(result));
|
|
299
|
+
writeGeneratedFile(ctx.root, ".prismo/context-firewall.suggested.md", renderFirewallPolicy(result));
|
|
300
|
+
writeGeneratedFile(ctx.root, ".prismo/allowed-context.suggested.txt", renderLines("Allowed Context Suggestions", result.allowed));
|
|
301
|
+
writeGeneratedFile(ctx.root, ".prismo/blocked-context.suggested.txt", renderLines("Blocked Context Suggestions", result.blocked));
|
|
302
|
+
}
|
|
303
|
+
result.dryRun = Boolean(options.dryRun);
|
|
304
|
+
return result;
|
|
305
|
+
}
|
|
306
|
+
|
|
190
307
|
function renderFirewallTerminal(result) {
|
|
191
308
|
const lines = [];
|
|
192
309
|
lines.push("");
|
|
@@ -218,6 +335,8 @@ function renderFirewallTerminal(result) {
|
|
|
218
335
|
renderFirewallPolicy,
|
|
219
336
|
renderFirewallPrompt,
|
|
220
337
|
renderFirewallTerminal,
|
|
338
|
+
renderTimelineFirewallSuggestions,
|
|
221
339
|
runFirewall,
|
|
340
|
+
runTimelineFirewallSuggestions,
|
|
222
341
|
};
|
|
223
342
|
};
|
package/lib/prismo-dev/mcp.js
CHANGED
|
@@ -31,6 +31,7 @@ function createMcpTools(deps) {
|
|
|
31
31
|
toDoctorJsonPayload,
|
|
32
32
|
getUsageSummary,
|
|
33
33
|
getClaudeCodeCostSummary,
|
|
34
|
+
getCursorSessionSummary,
|
|
34
35
|
runOptimize,
|
|
35
36
|
createOptimizeContext,
|
|
36
37
|
renderStarterPrompt,
|
|
@@ -106,6 +107,11 @@ function createMcpTools(deps) {
|
|
|
106
107
|
path: pathProperty,
|
|
107
108
|
limit: limitProperty,
|
|
108
109
|
}),
|
|
110
|
+
makeTool("prismo_cursor_sessions", "Return Cursor session data including AI authorship, conversations, and AI-generated file tracking.", {
|
|
111
|
+
path: pathProperty,
|
|
112
|
+
limit: limitProperty,
|
|
113
|
+
command: { type: "string", enum: ["latest", "list", "authorship", "timeline", "files"], description: "Subcommand: latest (summary), list (sessions), authorship (AI%), timeline (events), files (AI-generated)." },
|
|
114
|
+
}),
|
|
109
115
|
];
|
|
110
116
|
|
|
111
117
|
function resolveRoot(args) {
|
|
@@ -195,6 +201,15 @@ function createMcpTools(deps) {
|
|
|
195
201
|
}));
|
|
196
202
|
}
|
|
197
203
|
|
|
204
|
+
if (name === "prismo_cursor_sessions") {
|
|
205
|
+
if (!getCursorSessionSummary) throw new Error("Cursor session support not available");
|
|
206
|
+
return createTextResult(getCursorSessionSummary({
|
|
207
|
+
cwd: target,
|
|
208
|
+
limit: Number(args.limit) || 20,
|
|
209
|
+
mode: args.command || "latest",
|
|
210
|
+
}));
|
|
211
|
+
}
|
|
212
|
+
|
|
198
213
|
throw new Error(`Unknown MCP tool: ${name}`);
|
|
199
214
|
}
|
|
200
215
|
|
|
@@ -293,6 +308,7 @@ async function runMcpDoctor(deps) {
|
|
|
293
308
|
"prismo_context_pack",
|
|
294
309
|
"prismo_firewall",
|
|
295
310
|
"prismo_cc_timeline",
|
|
311
|
+
"prismo_cursor_sessions",
|
|
296
312
|
];
|
|
297
313
|
const toolNames = tools.map((tool) => tool.name);
|
|
298
314
|
const missingTools = requiredTools.filter((name) => !toolNames.includes(name));
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
module.exports = function createScanDetect(deps) {
|
|
2
2
|
const { fs, http, https, os, path, readIfText, estimateTokens, getClaudeSessionFiles, getCodexSessionFiles, normalizeRel } = deps;
|
|
3
3
|
|
|
4
|
+
let cursorSessionsModule = null;
|
|
5
|
+
function getCursorModule() {
|
|
6
|
+
if (!cursorSessionsModule) {
|
|
7
|
+
cursorSessionsModule = require("./cursor-sessions")({ fs, os, path, estimateTokens });
|
|
8
|
+
}
|
|
9
|
+
return cursorSessionsModule;
|
|
10
|
+
}
|
|
11
|
+
|
|
4
12
|
function countJsonObjectKeys(value, keyName) {
|
|
5
13
|
if (!value || typeof value !== "object") return 0;
|
|
6
14
|
let count = 0;
|
|
@@ -111,10 +119,11 @@ module.exports = function createScanDetect(deps) {
|
|
|
111
119
|
function detectAgentReadiness(root, claudeConfig, codexConfig, realUsage) {
|
|
112
120
|
const claudeHome = process.env.PRISMO_CLAUDE_HOME || path.join(os.homedir(), ".claude");
|
|
113
121
|
const codexHome = process.env.PRISMO_CODEX_HOME || path.join(os.homedir(), ".codex");
|
|
122
|
+
const cursorHome = process.env.PRISMO_CURSOR_HOME || path.join(os.homedir(), ".cursor");
|
|
114
123
|
const cursorPaths = [
|
|
115
124
|
path.join(root, ".cursor"),
|
|
116
125
|
path.join(root, ".cursorrules"),
|
|
117
|
-
|
|
126
|
+
cursorHome,
|
|
118
127
|
path.join(os.homedir(), ".config", "Cursor"),
|
|
119
128
|
];
|
|
120
129
|
const usageSources = new Set(realUsage && realUsage.sources ? realUsage.sources : []);
|
|
@@ -140,13 +149,26 @@ module.exports = function createScanDetect(deps) {
|
|
|
140
149
|
exactProxyTracking: "available-when-using-api-key-base-url-mode",
|
|
141
150
|
recommendedMode: "prismo-proxy-for-api-mode-or-local-log-watch",
|
|
142
151
|
},
|
|
143
|
-
cursor: {
|
|
144
|
-
detected
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
152
|
+
cursor: (() => {
|
|
153
|
+
const detected = pathExistsAny(cursorPaths);
|
|
154
|
+
const cursorMod = getCursorModule();
|
|
155
|
+
const dbAvailable = cursorMod.isSqlite3Available() && fs.existsSync(path.join(cursorHome, "ai-tracking", "ai-code-tracking.db"));
|
|
156
|
+
const dbStats = dbAvailable ? cursorMod.getAiTrackingDbStats() : null;
|
|
157
|
+
const workspace = detected ? cursorMod.getCursorWorkspaceForProject(root) : null;
|
|
158
|
+
const composers = dbAvailable ? cursorMod.getCursorComposerHeaders() : [];
|
|
159
|
+
return {
|
|
160
|
+
detected,
|
|
161
|
+
configFiles: cursorPaths.filter((candidate) => fs.existsSync(candidate)),
|
|
162
|
+
localLogsFound: dbAvailable && (dbStats ? (dbStats.ai_code_hashes + dbStats.scored_commits + dbStats.conversation_summaries) > 0 : false),
|
|
163
|
+
dbAvailable,
|
|
164
|
+
dbStats,
|
|
165
|
+
workspace,
|
|
166
|
+
totalSessions: composers.length,
|
|
167
|
+
activeSessions: composers.filter((c) => !c.isArchived).length,
|
|
168
|
+
exactProxyTracking: "available-only-if-configured-for-openai-compatible-base-url",
|
|
169
|
+
recommendedMode: dbAvailable ? "cursor-tracking-db-and-repo-scan" : "repo-scan-and-prismo-proxy-when-supported",
|
|
170
|
+
};
|
|
171
|
+
})(),
|
|
150
172
|
localUsageLogsAvailable: Boolean((realUsage && realUsage.sessions.length) || claudeSessionFiles.length || codexSessionFiles.length),
|
|
151
173
|
exactProxyTrackingAvailable: true,
|
|
152
174
|
notes: [
|
package/lib/prismo-dev/scan.js
CHANGED
|
@@ -229,7 +229,11 @@ function renderSetupTerminal(result) {
|
|
|
229
229
|
lines.push("Detected:");
|
|
230
230
|
lines.push(`- Claude Code: ${result.detected.claudeCode.detected ? "detected" : "not detected"}; logs: ${result.detected.claudeCode.localLogsFound ? "found" : "not found"}; MCP: ${result.detected.claudeCode.mcpServers}; hooks: ${result.detected.claudeCode.hooks}`);
|
|
231
231
|
lines.push(`- Codex: ${result.detected.codex.detected ? "detected" : "not detected"}; logs: ${result.detected.codex.localLogsFound ? "found" : "not found"}; MCP: ${result.detected.codex.mcpServers}`);
|
|
232
|
-
|
|
232
|
+
const cursorInfo = result.detected.cursor;
|
|
233
|
+
lines.push(`- Cursor: ${cursorInfo.detected ? "detected" : "not detected"}${cursorInfo.dbAvailable ? `; tracking DB: found (${cursorInfo.totalSessions} sessions, ${cursorInfo.activeSessions} active)` : ""}${cursorInfo.workspace ? `; workspace: matched` : ""}`);
|
|
234
|
+
if (cursorInfo.dbStats) {
|
|
235
|
+
lines.push(` Tracking: ${cursorInfo.dbStats.scored_commits} scored commits, ${cursorInfo.dbStats.ai_code_hashes} code hashes, ${cursorInfo.dbStats.conversation_summaries} conversation summaries`);
|
|
236
|
+
}
|
|
233
237
|
const detectedTools = result.detected.optimizationStack.detectedTools;
|
|
234
238
|
lines.push(`- Optimization tools: ${detectedTools.length ? detectedTools.join(", ") : "none detected"}`);
|
|
235
239
|
lines.push("");
|
|
@@ -562,6 +566,31 @@ function scanRepo(rootDir = process.cwd(), options = {}) {
|
|
|
562
566
|
}
|
|
563
567
|
const optimizationStack = detectOptimizationStack(root, claudeConfig, codexConfig);
|
|
564
568
|
const agentReadiness = detectAgentReadiness(root, claudeConfig, codexConfig, realUsage);
|
|
569
|
+
|
|
570
|
+
if (agentReadiness.cursor.dbAvailable) {
|
|
571
|
+
try {
|
|
572
|
+
const cursorMod = require("./cursor-sessions")({ fs, os, path, estimateTokens });
|
|
573
|
+
const tracked = cursorMod.getCursorTrackedFileContent(20);
|
|
574
|
+
const cursorAiFiles = tracked.filter((f) => {
|
|
575
|
+
const fullPath = path.join(root, f.gitPath);
|
|
576
|
+
return fs.existsSync(fullPath);
|
|
577
|
+
});
|
|
578
|
+
if (cursorAiFiles.length >= 3) {
|
|
579
|
+
addIssue(
|
|
580
|
+
issues,
|
|
581
|
+
"medium",
|
|
582
|
+
"ai_generated_files",
|
|
583
|
+
`${cursorAiFiles.length} AI-generated files still in repo (tracked by Cursor)`,
|
|
584
|
+
cursorAiFiles.slice(0, 5).map((f) => `${f.gitPath} (${f.model || "unknown model"})`).join(", "),
|
|
585
|
+
"Review AI-generated files for quality and whether they should remain in the project.",
|
|
586
|
+
`AI-generated content may inflate context if agents re-read it. Run: npx getprismo cursor files`
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
} catch {
|
|
590
|
+
// Cursor data unavailable
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
565
594
|
const toolOutputRisk = detectToolOutputRisk({ exposedLargeFiles, exposedHighRiskDirs, highRiskDirs });
|
|
566
595
|
const operationalNoise = detectOperationalNoise(files);
|
|
567
596
|
const proxyTrackingReadiness = buildProxyTrackingReadiness({ codexConfig, claudeConfig, realUsage });
|
|
@@ -9,6 +9,10 @@ module.exports = function createUsageSessions(deps) {
|
|
|
9
9
|
readIfText,
|
|
10
10
|
} = deps;
|
|
11
11
|
|
|
12
|
+
const {
|
|
13
|
+
analyzeCursorSessions,
|
|
14
|
+
} = require("./cursor-sessions")({ fs, os, path, estimateTokens });
|
|
15
|
+
|
|
12
16
|
const {
|
|
13
17
|
addUsage,
|
|
14
18
|
collectText,
|
|
@@ -261,6 +265,69 @@ function getUsageSummary(options = {}) {
|
|
|
261
265
|
sessions.push(analyzeSessionFile(file, "claude-code"));
|
|
262
266
|
}
|
|
263
267
|
}
|
|
268
|
+
if (tool === "all" || tool === "cursor") {
|
|
269
|
+
try {
|
|
270
|
+
const cursorData = analyzeCursorSessions({ limit, cwd });
|
|
271
|
+
if (cursorData.sessions.length) {
|
|
272
|
+
for (const cursorSession of cursorData.sessions.slice(0, limit)) {
|
|
273
|
+
sessions.push({
|
|
274
|
+
tool: "cursor",
|
|
275
|
+
filePath: null,
|
|
276
|
+
sessionId: cursorSession.sessionId,
|
|
277
|
+
title: cursorSession.title,
|
|
278
|
+
cwd: cwd,
|
|
279
|
+
model: cursorSession.model,
|
|
280
|
+
startedAt: cursorSession.createdAt,
|
|
281
|
+
updatedAt: cursorSession.updatedAt || cursorSession.createdAt,
|
|
282
|
+
turns: 0,
|
|
283
|
+
userMessages: 0,
|
|
284
|
+
assistantMessages: 0,
|
|
285
|
+
toolCalls: 0,
|
|
286
|
+
toolResults: 0,
|
|
287
|
+
estimatedInputTokens: 0,
|
|
288
|
+
estimatedOutputTokens: 0,
|
|
289
|
+
estimatedToolTokens: 0,
|
|
290
|
+
inputTokens: 0,
|
|
291
|
+
outputTokens: 0,
|
|
292
|
+
cacheReadTokens: 0,
|
|
293
|
+
cacheCreationTokens: 0,
|
|
294
|
+
exactInputTokens: 0,
|
|
295
|
+
exactOutputTokens: 0,
|
|
296
|
+
exactCacheReadTokens: 0,
|
|
297
|
+
exactCacheCreationTokens: 0,
|
|
298
|
+
exactTotalTokens: 0,
|
|
299
|
+
exactAvailable: false,
|
|
300
|
+
confidence: "cursor-metadata",
|
|
301
|
+
largestTextBlobs: [],
|
|
302
|
+
toolNames: {},
|
|
303
|
+
pathMentions: {},
|
|
304
|
+
generatedArtifactMentions: {},
|
|
305
|
+
commandMentions: {},
|
|
306
|
+
failureMentions: 0,
|
|
307
|
+
eventTokenDeltas: [],
|
|
308
|
+
exactTokenTimeline: [],
|
|
309
|
+
estimatedTotalTokens: 0,
|
|
310
|
+
exactActiveTokens: 0,
|
|
311
|
+
contextTokens: 0,
|
|
312
|
+
displayTokens: 0,
|
|
313
|
+
contextRisk: "Low",
|
|
314
|
+
recentContextGrowth: 0,
|
|
315
|
+
repeatedPathMentions: [],
|
|
316
|
+
generatedArtifacts: [],
|
|
317
|
+
repeatedCommands: [],
|
|
318
|
+
loopSuspicion: false,
|
|
319
|
+
loopConfidence: "low",
|
|
320
|
+
cost: null,
|
|
321
|
+
cursorMode: cursorSession.mode,
|
|
322
|
+
cursorLinesAdded: cursorSession.linesAdded,
|
|
323
|
+
cursorLinesRemoved: cursorSession.linesRemoved,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
} catch {
|
|
328
|
+
// Cursor data unavailable
|
|
329
|
+
}
|
|
330
|
+
}
|
|
264
331
|
sessions.sort((a, b) => new Date(b.updatedAt || 0) - new Date(a.updatedAt || 0));
|
|
265
332
|
const selected = sessions.slice(0, limit);
|
|
266
333
|
const totals = selected.reduce(
|
|
@@ -290,6 +357,7 @@ function getUsageSummary(options = {}) {
|
|
|
290
357
|
|
|
291
358
|
return {
|
|
292
359
|
analyzeSessionFile,
|
|
360
|
+
analyzeCursorSessions,
|
|
293
361
|
getAllClaudeSessionFiles,
|
|
294
362
|
getClaudeSessionFiles,
|
|
295
363
|
getCodexSessionFiles,
|
|
@@ -63,6 +63,7 @@ const {
|
|
|
63
63
|
|
|
64
64
|
const {
|
|
65
65
|
analyzeSessionFile,
|
|
66
|
+
analyzeCursorSessions,
|
|
66
67
|
getAllClaudeSessionFiles,
|
|
67
68
|
getClaudeSessionFiles,
|
|
68
69
|
getCodexSessionFiles,
|
|
@@ -77,6 +78,12 @@ const {
|
|
|
77
78
|
readIfText,
|
|
78
79
|
});
|
|
79
80
|
|
|
81
|
+
const {
|
|
82
|
+
buildCursorDiagnosis,
|
|
83
|
+
buildCursorSessionTimeline,
|
|
84
|
+
renderCursorTerminal,
|
|
85
|
+
} = require("./cursor-sessions")({ fs, os, path, estimateTokens });
|
|
86
|
+
|
|
80
87
|
function getUsageSummary(options = {}) {
|
|
81
88
|
const summary = getBaseUsageSummary(options);
|
|
82
89
|
if ((summary.sessions || []).length > 1) {
|
|
@@ -176,6 +183,17 @@ function getClaudeCodeCostSummary(options = {}) {
|
|
|
176
183
|
};
|
|
177
184
|
}
|
|
178
185
|
|
|
186
|
+
function getCursorSessionSummary(options = {}) {
|
|
187
|
+
const cwd = options.cwd || process.cwd();
|
|
188
|
+
const limit = options.limit || 20;
|
|
189
|
+
const mode = options.mode || "latest";
|
|
190
|
+
const cursorData = analyzeCursorSessions({ limit, cwd });
|
|
191
|
+
cursorData.command = mode;
|
|
192
|
+
cursorData.timeline = buildCursorSessionTimeline(cursorData);
|
|
193
|
+
cursorData.diagnosis = buildCursorDiagnosis(cursorData);
|
|
194
|
+
return cursorData;
|
|
195
|
+
}
|
|
196
|
+
|
|
179
197
|
function parsePositiveInt(value, fallback) {
|
|
180
198
|
const parsed = Number.parseInt(value, 10);
|
|
181
199
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
@@ -550,6 +568,9 @@ async function watchUsage(options = {}) {
|
|
|
550
568
|
|
|
551
569
|
return {
|
|
552
570
|
analyzeSessionFile,
|
|
571
|
+
analyzeCursorSessions,
|
|
572
|
+
buildCursorDiagnosis,
|
|
573
|
+
buildCursorSessionTimeline,
|
|
553
574
|
calculateClaudeCost,
|
|
554
575
|
compactUsageSummary,
|
|
555
576
|
formatMoney,
|
|
@@ -557,11 +578,13 @@ async function watchUsage(options = {}) {
|
|
|
557
578
|
getClaudeCodeCostSummary,
|
|
558
579
|
getClaudeSessionFiles,
|
|
559
580
|
getCodexSessionFiles,
|
|
581
|
+
getCursorSessionSummary,
|
|
560
582
|
getUsageSummary,
|
|
561
583
|
getPositionals,
|
|
562
584
|
parsePositiveInt,
|
|
563
585
|
parseScopeAndTarget,
|
|
564
586
|
renderClaudeCostTerminal,
|
|
587
|
+
renderCursorTerminal,
|
|
565
588
|
renderUsageTerminal,
|
|
566
589
|
renderContextThrottle,
|
|
567
590
|
buildWatchEvent,
|