getprismo 0.1.27 → 0.1.29
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 +53 -3
- package/lib/prismo-dev/cursor-sessions.js +641 -0
- package/lib/prismo-dev/mcp.js +16 -0
- package/lib/prismo-dev/scan-detect.js +30 -8
- package/lib/prismo-dev/scan.js +31 -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 +66 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# prismodev
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/getprismo)
|
|
4
|
+
[](https://www.npmjs.com/package/getprismo)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
3
7
|
local ai coding cost control. one command to diagnose token waste, fix it, and prove the improvement.
|
|
4
8
|
|
|
5
9
|
```bash
|
|
@@ -545,9 +549,49 @@ it only creates new files and recommendations. you decide what to apply.
|
|
|
545
549
|
|
|
546
550
|
---
|
|
547
551
|
|
|
552
|
+
## cursor session tracking
|
|
553
|
+
|
|
554
|
+
prismodev now reads cursor's local sqlite databases directly. cursor stores data differently from claude code and codex, no jsonl session logs, but it has its own tracking databases with unique data.
|
|
555
|
+
|
|
556
|
+
```bash
|
|
557
|
+
npx getprismo cursor # summary of all cursor sessions
|
|
558
|
+
npx getprismo cursor list # list composer sessions with modes and models
|
|
559
|
+
npx getprismo cursor authorship # ai vs human code authorship from scored commits
|
|
560
|
+
npx getprismo cursor timeline # timeline of ai activity across commits and files
|
|
561
|
+
npx getprismo cursor files # ai-generated and ai-deleted file tracking
|
|
562
|
+
npx getprismo cursor --json # machine-readable output
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
cursor tracks something claude code and codex can't: per-commit ai authorship. every commit is scored with how many lines came from composer (agent), tab completions, and human typing. prismodev surfaces this as an authorship percentage.
|
|
566
|
+
|
|
567
|
+
```
|
|
568
|
+
AI Authorship (from Cursor scored commits)
|
|
569
|
+
|
|
570
|
+
Commits analyzed: 47
|
|
571
|
+
Total lines added: 3812
|
|
572
|
+
|
|
573
|
+
Composer (agent): 2104 lines
|
|
574
|
+
Tab completions: 891 lines
|
|
575
|
+
Human: 817 lines
|
|
576
|
+
--------------------------------------------------
|
|
577
|
+
AI authorship: 78%
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
prismodev also tracks ai-generated files cursor is watching, files cursor deleted, conversation summaries, and model usage distribution across sessions.
|
|
581
|
+
|
|
582
|
+
what cursor can't do vs claude code: cursor doesn't expose per-message token counts, exact api costs, or full conversation transcripts in its local data. that means live context pressure, loop detection, exact cost breakdowns, cache savings analysis, and shield don't apply the same way. this is a cursor limitation. prismodev gets about 60-65% feature parity with cursor compared to claude code/codex.
|
|
583
|
+
|
|
584
|
+
what cursor gives you that the others don't: ai authorship percentages per commit, tab vs composer vs human line counts, conversation summaries with tldr, and ai-generated file tracking with churn detection.
|
|
585
|
+
|
|
586
|
+
the `prismo_cursor_sessions` mcp tool exposes all of this to compatible agents.
|
|
587
|
+
|
|
588
|
+
`scan` and `doctor` now detect cursor's tracking database automatically and flag ai-generated files still present in the repo.
|
|
589
|
+
|
|
590
|
+
---
|
|
591
|
+
|
|
548
592
|
## how watch catches waste live
|
|
549
593
|
|
|
550
|
-
watch reads local session logs from codex
|
|
594
|
+
watch reads local session logs from codex, claude code, and cursor. it detects:
|
|
551
595
|
|
|
552
596
|
| signal | what it means |
|
|
553
597
|
|--------|--------------|
|
|
@@ -623,6 +667,7 @@ no install needed. npx runs it directly.
|
|
|
623
667
|
| `watch` | live session monitoring with warnings |
|
|
624
668
|
| `cc` | claude code cost breakdown |
|
|
625
669
|
| `cc timeline` | session reconstruction with events |
|
|
670
|
+
| `cursor` | cursor session tracking and ai authorship |
|
|
626
671
|
| `scan --usage` | full repo scan with local usage data |
|
|
627
672
|
| `scan --optimizer-fit` | recommend which token-optimization path fits your repo/session |
|
|
628
673
|
| `scan --report-card` | shortest decision-layer summary |
|
|
@@ -677,6 +722,7 @@ npx getprismo watch --rescue --json # include rescuePrompt in JSON
|
|
|
677
722
|
npx getprismo watch --once --redact-paths # hide local paths
|
|
678
723
|
npx getprismo watch codex # only codex sessions
|
|
679
724
|
npx getprismo watch claude # only claude code sessions
|
|
725
|
+
npx getprismo watch cursor # only cursor sessions
|
|
680
726
|
```
|
|
681
727
|
|
|
682
728
|
### shield mode
|
|
@@ -708,6 +754,7 @@ npx getprismo mcp /path/to/repo
|
|
|
708
754
|
- `prismo_context_pack`
|
|
709
755
|
- `prismo_firewall`
|
|
710
756
|
- `prismo_cc_timeline`
|
|
757
|
+
- `prismo_cursor_sessions`
|
|
711
758
|
|
|
712
759
|
This lets an MCP-compatible agent search prior shielded test/build output, request scoped context packs, inspect token-waste signals, or coordinate multiple local agents without pasting giant logs into the conversation.
|
|
713
760
|
|
|
@@ -802,9 +849,10 @@ local logs exact when codex/claude session logs expose token fields
|
|
|
802
849
|
prismo proxy exact usage/cost when traffic routes through prismo base url
|
|
803
850
|
```
|
|
804
851
|
|
|
805
|
-
prismodev reads local session
|
|
852
|
+
prismodev reads local session data from:
|
|
806
853
|
- codex: `~/.codex/sessions/**/*.jsonl`
|
|
807
854
|
- claude code: `~/.claude/projects/**/*.jsonl`
|
|
855
|
+
- cursor: `~/.cursor/ai-tracking/ai-code-tracking.db` and `~/Library/Application Support/Cursor/User/globalStorage/state.vscdb`
|
|
808
856
|
|
|
809
857
|
no api keys. no intercepted prompts. no data uploaded.
|
|
810
858
|
|
|
@@ -890,7 +938,8 @@ lib/prismo-dev/scan-path-utils.js scan ignore/path helper logic
|
|
|
890
938
|
lib/prismo-dev/shield.js local command shield and searchable output index
|
|
891
939
|
lib/prismo-dev/usage-cost.js Claude Code cost and timeline analysis
|
|
892
940
|
lib/prismo-dev/usage-log-utils.js local session log parsing helpers
|
|
893
|
-
lib/prismo-dev/
|
|
941
|
+
lib/prismo-dev/cursor-sessions.js Cursor SQLite session and authorship tracking
|
|
942
|
+
lib/prismo-dev/usage-sessions.js local Codex/Claude/Cursor session discovery
|
|
894
943
|
lib/prismo-dev/usage-watch.js watch orchestration, JSON payloads, live files
|
|
895
944
|
lib/prismo-dev/utils.js shared terminal/file/token helpers
|
|
896
945
|
lib/prismo-dev/watch-live.js live context-pressure decisions
|
|
@@ -910,6 +959,7 @@ npx getprismo shield --help
|
|
|
910
959
|
npx getprismo mcp --help
|
|
911
960
|
npx getprismo mcp doctor
|
|
912
961
|
npx getprismo cc --help
|
|
962
|
+
npx getprismo cursor --help
|
|
913
963
|
npx getprismo scan --help
|
|
914
964
|
```
|
|
915
965
|
|
|
@@ -0,0 +1,641 @@
|
|
|
1
|
+
module.exports = function createCursorSessions(deps) {
|
|
2
|
+
const { fs, os, path, estimateTokens } = deps;
|
|
3
|
+
|
|
4
|
+
function getCursorHome() {
|
|
5
|
+
return process.env.PRISMO_CURSOR_HOME || path.join(os.homedir(), ".cursor");
|
|
6
|
+
}
|
|
7
|
+
function getCursorAppSupport() {
|
|
8
|
+
return process.env.PRISMO_CURSOR_APP_SUPPORT || path.join(os.homedir(), "Library", "Application Support", "Cursor");
|
|
9
|
+
}
|
|
10
|
+
function getAiTrackingDbPath() {
|
|
11
|
+
return path.join(getCursorHome(), "ai-tracking", "ai-code-tracking.db");
|
|
12
|
+
}
|
|
13
|
+
function getGlobalStateDbPath() {
|
|
14
|
+
return path.join(getCursorAppSupport(), "User", "globalStorage", "state.vscdb");
|
|
15
|
+
}
|
|
16
|
+
function getIdeStatePath() {
|
|
17
|
+
return path.join(getCursorHome(), "ide_state.json");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let sqlite3Available = null;
|
|
21
|
+
let spawnSyncFn = null;
|
|
22
|
+
|
|
23
|
+
function getSpawnSync() {
|
|
24
|
+
if (!spawnSyncFn) {
|
|
25
|
+
spawnSyncFn = require("child_process").spawnSync;
|
|
26
|
+
}
|
|
27
|
+
return spawnSyncFn;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function isSqlite3Available() {
|
|
31
|
+
if (sqlite3Available !== null) return sqlite3Available;
|
|
32
|
+
try {
|
|
33
|
+
const result = getSpawnSync()("sqlite3", ["--version"], { timeout: 3000, stdio: "pipe" });
|
|
34
|
+
sqlite3Available = result.status === 0;
|
|
35
|
+
} catch {
|
|
36
|
+
sqlite3Available = false;
|
|
37
|
+
}
|
|
38
|
+
return sqlite3Available;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function querySqlite(dbPath, sql) {
|
|
42
|
+
if (!isSqlite3Available()) return [];
|
|
43
|
+
if (!fs.existsSync(dbPath)) return [];
|
|
44
|
+
try {
|
|
45
|
+
const result = getSpawnSync()("sqlite3", ["-json", dbPath, sql], {
|
|
46
|
+
timeout: 10000,
|
|
47
|
+
stdio: "pipe",
|
|
48
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
49
|
+
});
|
|
50
|
+
if (result.status !== 0) return [];
|
|
51
|
+
const output = (result.stdout || "").toString().trim();
|
|
52
|
+
if (!output) return [];
|
|
53
|
+
return JSON.parse(output);
|
|
54
|
+
} catch {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function querySqliteCsv(dbPath, sql) {
|
|
60
|
+
if (!isSqlite3Available()) return [];
|
|
61
|
+
if (!fs.existsSync(dbPath)) return [];
|
|
62
|
+
try {
|
|
63
|
+
const result = getSpawnSync()("sqlite3", ["-header", "-csv", dbPath, sql], {
|
|
64
|
+
timeout: 10000,
|
|
65
|
+
stdio: "pipe",
|
|
66
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
67
|
+
});
|
|
68
|
+
if (result.status !== 0) return [];
|
|
69
|
+
const output = (result.stdout || "").toString().trim();
|
|
70
|
+
if (!output) return [];
|
|
71
|
+
const lines = output.split(/\r?\n/);
|
|
72
|
+
if (lines.length < 2) return [];
|
|
73
|
+
const headers = parseCsvLine(lines[0]);
|
|
74
|
+
return lines.slice(1).map((line) => {
|
|
75
|
+
const values = parseCsvLine(line);
|
|
76
|
+
const row = {};
|
|
77
|
+
headers.forEach((header, i) => { row[header] = values[i] || ""; });
|
|
78
|
+
return row;
|
|
79
|
+
});
|
|
80
|
+
} catch {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function parseCsvLine(line) {
|
|
86
|
+
const values = [];
|
|
87
|
+
let current = "";
|
|
88
|
+
let inQuotes = false;
|
|
89
|
+
for (let i = 0; i < line.length; i++) {
|
|
90
|
+
const ch = line[i];
|
|
91
|
+
if (inQuotes) {
|
|
92
|
+
if (ch === '"' && line[i + 1] === '"') {
|
|
93
|
+
current += '"';
|
|
94
|
+
i++;
|
|
95
|
+
} else if (ch === '"') {
|
|
96
|
+
inQuotes = false;
|
|
97
|
+
} else {
|
|
98
|
+
current += ch;
|
|
99
|
+
}
|
|
100
|
+
} else if (ch === '"') {
|
|
101
|
+
inQuotes = true;
|
|
102
|
+
} else if (ch === ",") {
|
|
103
|
+
values.push(current);
|
|
104
|
+
current = "";
|
|
105
|
+
} else {
|
|
106
|
+
current += ch;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
values.push(current);
|
|
110
|
+
return values;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function queryDb(dbPath, sql) {
|
|
114
|
+
const rows = querySqlite(dbPath, sql);
|
|
115
|
+
if (rows.length) return rows;
|
|
116
|
+
return querySqliteCsv(dbPath, sql);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function getCursorScoredCommits(limit = 50) {
|
|
120
|
+
const sql = `SELECT branchName, commitHash, commitMessage, commitDate,
|
|
121
|
+
linesAdded, linesDeleted, tabLinesAdded, tabLinesDeleted,
|
|
122
|
+
composerLinesAdded, composerLinesDeleted,
|
|
123
|
+
humanLinesAdded, humanLinesDeleted,
|
|
124
|
+
blankLinesAdded, blankLinesDeleted,
|
|
125
|
+
v1AiPercentage, v2AiPercentage, scoredAt
|
|
126
|
+
FROM scored_commits ORDER BY scoredAt DESC LIMIT ${limit}`;
|
|
127
|
+
return queryDb(getAiTrackingDbPath(), sql).map((row) => ({
|
|
128
|
+
branchName: row.branchName || "",
|
|
129
|
+
commitHash: row.commitHash || "",
|
|
130
|
+
commitMessage: row.commitMessage || "",
|
|
131
|
+
commitDate: row.commitDate || "",
|
|
132
|
+
linesAdded: Number(row.linesAdded) || 0,
|
|
133
|
+
linesDeleted: Number(row.linesDeleted) || 0,
|
|
134
|
+
tabLinesAdded: Number(row.tabLinesAdded) || 0,
|
|
135
|
+
tabLinesDeleted: Number(row.tabLinesDeleted) || 0,
|
|
136
|
+
composerLinesAdded: Number(row.composerLinesAdded) || 0,
|
|
137
|
+
composerLinesDeleted: Number(row.composerLinesDeleted) || 0,
|
|
138
|
+
humanLinesAdded: Number(row.humanLinesAdded) || 0,
|
|
139
|
+
humanLinesDeleted: Number(row.humanLinesDeleted) || 0,
|
|
140
|
+
blankLinesAdded: Number(row.blankLinesAdded) || 0,
|
|
141
|
+
blankLinesDeleted: Number(row.blankLinesDeleted) || 0,
|
|
142
|
+
v1AiPercentage: row.v1AiPercentage || "",
|
|
143
|
+
v2AiPercentage: row.v2AiPercentage || "",
|
|
144
|
+
scoredAt: Number(row.scoredAt) || 0,
|
|
145
|
+
}));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function getCursorAiCodeHashes(limit = 100) {
|
|
149
|
+
const sql = `SELECT hash, source, fileExtension, fileName, requestId,
|
|
150
|
+
conversationId, timestamp, model, createdAt
|
|
151
|
+
FROM ai_code_hashes ORDER BY createdAt DESC LIMIT ${limit}`;
|
|
152
|
+
return queryDb(getAiTrackingDbPath(), sql).map((row) => ({
|
|
153
|
+
hash: row.hash || "",
|
|
154
|
+
source: row.source || "",
|
|
155
|
+
fileExtension: row.fileExtension || "",
|
|
156
|
+
fileName: row.fileName || "",
|
|
157
|
+
requestId: row.requestId || "",
|
|
158
|
+
conversationId: row.conversationId || "",
|
|
159
|
+
timestamp: Number(row.timestamp) || 0,
|
|
160
|
+
model: row.model || "",
|
|
161
|
+
createdAt: Number(row.createdAt) || 0,
|
|
162
|
+
}));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function getCursorConversationSummaries(limit = 50) {
|
|
166
|
+
const sql = `SELECT conversationId, title, tldr, overview, summaryBullets,
|
|
167
|
+
model, mode, updatedAt
|
|
168
|
+
FROM conversation_summaries ORDER BY updatedAt DESC LIMIT ${limit}`;
|
|
169
|
+
return queryDb(getAiTrackingDbPath(), sql).map((row) => ({
|
|
170
|
+
conversationId: row.conversationId || "",
|
|
171
|
+
title: row.title || "",
|
|
172
|
+
tldr: row.tldr || "",
|
|
173
|
+
overview: row.overview || "",
|
|
174
|
+
summaryBullets: row.summaryBullets || "",
|
|
175
|
+
model: row.model || "",
|
|
176
|
+
mode: row.mode || "",
|
|
177
|
+
updatedAt: Number(row.updatedAt) || 0,
|
|
178
|
+
}));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function getCursorTrackedFileContent(limit = 50) {
|
|
182
|
+
const sql = `SELECT gitPath, conversationId, model, fileExtension, createdAt,
|
|
183
|
+
length(content) as contentLength
|
|
184
|
+
FROM tracked_file_content ORDER BY createdAt DESC LIMIT ${limit}`;
|
|
185
|
+
return queryDb(getAiTrackingDbPath(), sql).map((row) => ({
|
|
186
|
+
gitPath: row.gitPath || "",
|
|
187
|
+
conversationId: row.conversationId || "",
|
|
188
|
+
model: row.model || "",
|
|
189
|
+
fileExtension: row.fileExtension || "",
|
|
190
|
+
createdAt: Number(row.createdAt) || 0,
|
|
191
|
+
contentLength: Number(row.contentLength) || 0,
|
|
192
|
+
}));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function getCursorDeletedFiles(limit = 50) {
|
|
196
|
+
const sql = `SELECT gitPath, composerId, conversationId, model, deletedAt
|
|
197
|
+
FROM ai_deleted_files ORDER BY deletedAt DESC LIMIT ${limit}`;
|
|
198
|
+
return queryDb(getAiTrackingDbPath(), sql).map((row) => ({
|
|
199
|
+
gitPath: row.gitPath || "",
|
|
200
|
+
composerId: row.composerId || "",
|
|
201
|
+
conversationId: row.conversationId || "",
|
|
202
|
+
model: row.model || "",
|
|
203
|
+
deletedAt: Number(row.deletedAt) || 0,
|
|
204
|
+
}));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function getCursorComposerHeaders() {
|
|
208
|
+
const rows = queryDb(getGlobalStateDbPath(),
|
|
209
|
+
"SELECT value FROM ItemTable WHERE key = 'composer.composerHeaders'");
|
|
210
|
+
if (!rows.length) return [];
|
|
211
|
+
try {
|
|
212
|
+
const raw = rows[0].value || rows[0];
|
|
213
|
+
const data = typeof raw === "string" ? JSON.parse(raw) : raw;
|
|
214
|
+
return (data.allComposers || []).map((c) => ({
|
|
215
|
+
composerId: c.composerId || "",
|
|
216
|
+
createdAt: Number(c.createdAt) || 0,
|
|
217
|
+
mode: c.unifiedMode || c.forceMode || "",
|
|
218
|
+
linesAdded: Number(c.totalLinesAdded) || 0,
|
|
219
|
+
linesRemoved: Number(c.totalLinesRemoved) || 0,
|
|
220
|
+
isArchived: Boolean(c.isArchived),
|
|
221
|
+
isDraft: Boolean(c.isDraft),
|
|
222
|
+
isWorktree: Boolean(c.isWorktree),
|
|
223
|
+
isSpec: Boolean(c.isSpec),
|
|
224
|
+
numSubComposers: Number(c.numSubComposers) || 0,
|
|
225
|
+
workspaceId: c.workspaceIdentifier?.id || "",
|
|
226
|
+
}));
|
|
227
|
+
} catch {
|
|
228
|
+
return [];
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function getCursorWorkspaceForProject(projectPath) {
|
|
233
|
+
const wsDir = path.join(getCursorAppSupport(), "User", "workspaceStorage");
|
|
234
|
+
if (!fs.existsSync(wsDir)) return null;
|
|
235
|
+
try {
|
|
236
|
+
const entries = fs.readdirSync(wsDir);
|
|
237
|
+
for (const entry of entries) {
|
|
238
|
+
const wsJson = path.join(wsDir, entry, "workspace.json");
|
|
239
|
+
if (!fs.existsSync(wsJson)) continue;
|
|
240
|
+
try {
|
|
241
|
+
const data = JSON.parse(fs.readFileSync(wsJson, "utf8"));
|
|
242
|
+
const folder = decodeURIComponent((data.folder || "").replace("file://", ""));
|
|
243
|
+
if (folder && path.resolve(folder) === path.resolve(projectPath)) {
|
|
244
|
+
return { workspaceId: entry, folder };
|
|
245
|
+
}
|
|
246
|
+
} catch {
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
} catch {
|
|
251
|
+
// workspace dir not readable
|
|
252
|
+
}
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function getCursorIdeState() {
|
|
257
|
+
if (!fs.existsSync(getIdeStatePath())) return null;
|
|
258
|
+
try {
|
|
259
|
+
return JSON.parse(fs.readFileSync(getIdeStatePath(), "utf8"));
|
|
260
|
+
} catch {
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function getAiTrackingDbStats() {
|
|
266
|
+
if (!fs.existsSync(getAiTrackingDbPath())) return null;
|
|
267
|
+
const counts = {};
|
|
268
|
+
const tables = ["ai_code_hashes", "conversation_summaries", "scored_commits", "tracked_file_content", "ai_deleted_files"];
|
|
269
|
+
for (const table of tables) {
|
|
270
|
+
const rows = queryDb(getAiTrackingDbPath(), `SELECT COUNT(*) as count FROM ${table}`);
|
|
271
|
+
counts[table] = rows.length ? Number(rows[0].count) || 0 : 0;
|
|
272
|
+
}
|
|
273
|
+
return counts;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function analyzeCursorSessions(options = {}) {
|
|
277
|
+
const limit = options.limit || 20;
|
|
278
|
+
const cwd = options.cwd || process.cwd();
|
|
279
|
+
|
|
280
|
+
const composers = getCursorComposerHeaders();
|
|
281
|
+
const summaries = getCursorConversationSummaries(limit);
|
|
282
|
+
const scoredCommits = getCursorScoredCommits(limit);
|
|
283
|
+
const aiHashes = getCursorAiCodeHashes(limit);
|
|
284
|
+
const trackedFiles = getCursorTrackedFileContent(limit);
|
|
285
|
+
const deletedFiles = getCursorDeletedFiles(limit);
|
|
286
|
+
const dbStats = getAiTrackingDbStats();
|
|
287
|
+
const workspace = getCursorWorkspaceForProject(cwd);
|
|
288
|
+
|
|
289
|
+
const summaryMap = new Map();
|
|
290
|
+
for (const s of summaries) {
|
|
291
|
+
summaryMap.set(s.conversationId, s);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const sessions = composers
|
|
295
|
+
.sort((a, b) => b.createdAt - a.createdAt)
|
|
296
|
+
.slice(0, limit)
|
|
297
|
+
.map((composer) => {
|
|
298
|
+
const summary = summaryMap.get(composer.composerId);
|
|
299
|
+
return {
|
|
300
|
+
tool: "cursor",
|
|
301
|
+
sessionId: composer.composerId,
|
|
302
|
+
title: summary?.title || "",
|
|
303
|
+
tldr: summary?.tldr || "",
|
|
304
|
+
model: summary?.model || "",
|
|
305
|
+
mode: composer.mode || summary?.mode || "",
|
|
306
|
+
createdAt: composer.createdAt ? new Date(composer.createdAt).toISOString() : null,
|
|
307
|
+
updatedAt: summary?.updatedAt ? new Date(summary.updatedAt).toISOString() : null,
|
|
308
|
+
linesAdded: composer.linesAdded,
|
|
309
|
+
linesRemoved: composer.linesRemoved,
|
|
310
|
+
isArchived: composer.isArchived,
|
|
311
|
+
isDraft: composer.isDraft,
|
|
312
|
+
isWorktree: composer.isWorktree,
|
|
313
|
+
numSubComposers: composer.numSubComposers,
|
|
314
|
+
workspaceId: composer.workspaceId,
|
|
315
|
+
confidence: "cursor-metadata",
|
|
316
|
+
};
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
const totalLinesAdded = scoredCommits.reduce((sum, c) => sum + c.linesAdded, 0);
|
|
320
|
+
const totalComposerLinesAdded = scoredCommits.reduce((sum, c) => sum + c.composerLinesAdded, 0);
|
|
321
|
+
const totalTabLinesAdded = scoredCommits.reduce((sum, c) => sum + c.tabLinesAdded, 0);
|
|
322
|
+
const totalHumanLinesAdded = scoredCommits.reduce((sum, c) => sum + c.humanLinesAdded, 0);
|
|
323
|
+
const aiLinesAdded = totalComposerLinesAdded + totalTabLinesAdded;
|
|
324
|
+
const aiAuthorshipPercent = totalLinesAdded > 0 ? Math.round((aiLinesAdded / totalLinesAdded) * 100) : 0;
|
|
325
|
+
|
|
326
|
+
const modelDistribution = {};
|
|
327
|
+
for (const h of aiHashes) {
|
|
328
|
+
if (h.model) modelDistribution[h.model] = (modelDistribution[h.model] || 0) + 1;
|
|
329
|
+
}
|
|
330
|
+
const modeDistribution = {};
|
|
331
|
+
for (const c of composers) {
|
|
332
|
+
if (c.mode) modeDistribution[c.mode] = (modeDistribution[c.mode] || 0) + 1;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const aiGeneratedFiles = trackedFiles.map((f) => ({
|
|
336
|
+
gitPath: f.gitPath,
|
|
337
|
+
model: f.model,
|
|
338
|
+
contentLength: f.contentLength,
|
|
339
|
+
estimatedTokens: estimateTokens(f.contentLength),
|
|
340
|
+
createdAt: f.createdAt ? new Date(f.createdAt).toISOString() : null,
|
|
341
|
+
}));
|
|
342
|
+
|
|
343
|
+
const aiDeletedFileList = deletedFiles.map((f) => ({
|
|
344
|
+
gitPath: f.gitPath,
|
|
345
|
+
model: f.model,
|
|
346
|
+
deletedAt: f.deletedAt ? new Date(f.deletedAt).toISOString() : null,
|
|
347
|
+
}));
|
|
348
|
+
|
|
349
|
+
return {
|
|
350
|
+
generatedAt: new Date().toISOString(),
|
|
351
|
+
scannedPath: cwd,
|
|
352
|
+
tool: "cursor",
|
|
353
|
+
dbAvailable: fs.existsSync(getAiTrackingDbPath()),
|
|
354
|
+
sqlite3Available: isSqlite3Available(),
|
|
355
|
+
dbStats,
|
|
356
|
+
workspace,
|
|
357
|
+
sessions,
|
|
358
|
+
scoredCommits: scoredCommits.map((c) => ({
|
|
359
|
+
...c,
|
|
360
|
+
scoredAt: c.scoredAt ? new Date(c.scoredAt).toISOString() : null,
|
|
361
|
+
})),
|
|
362
|
+
aiAuthorship: {
|
|
363
|
+
totalCommits: scoredCommits.length,
|
|
364
|
+
totalLinesAdded,
|
|
365
|
+
aiLinesAdded,
|
|
366
|
+
humanLinesAdded: totalHumanLinesAdded,
|
|
367
|
+
composerLinesAdded: totalComposerLinesAdded,
|
|
368
|
+
tabLinesAdded: totalTabLinesAdded,
|
|
369
|
+
aiAuthorshipPercent,
|
|
370
|
+
},
|
|
371
|
+
aiGeneratedFiles,
|
|
372
|
+
aiDeletedFiles: aiDeletedFileList,
|
|
373
|
+
modelDistribution,
|
|
374
|
+
modeDistribution,
|
|
375
|
+
totalSessions: composers.length,
|
|
376
|
+
activeSessions: composers.filter((c) => !c.isArchived).length,
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function buildCursorSessionTimeline(cursorData) {
|
|
381
|
+
const events = [];
|
|
382
|
+
|
|
383
|
+
for (const commit of (cursorData.scoredCommits || []).slice(0, 10)) {
|
|
384
|
+
const aiLines = (commit.composerLinesAdded || 0) + (commit.tabLinesAdded || 0);
|
|
385
|
+
const totalLines = commit.linesAdded || 0;
|
|
386
|
+
const pct = totalLines > 0 ? Math.round((aiLines / totalLines) * 100) : 0;
|
|
387
|
+
if (totalLines > 0) {
|
|
388
|
+
events.push({
|
|
389
|
+
timestamp: commit.scoredAt || commit.commitDate || null,
|
|
390
|
+
type: pct >= 80 ? "high-ai-authorship" : pct >= 40 ? "mixed-authorship" : "human-authorship",
|
|
391
|
+
label: `Commit: ${(commit.commitMessage || commit.commitHash || "").slice(0, 60)}`,
|
|
392
|
+
detail: `+${totalLines}/-${commit.linesDeleted || 0} lines, ${pct}% AI (composer: ${commit.composerLinesAdded}, tab: ${commit.tabLinesAdded}, human: ${commit.humanLinesAdded})`,
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
for (const file of (cursorData.aiGeneratedFiles || []).slice(0, 5)) {
|
|
398
|
+
events.push({
|
|
399
|
+
timestamp: file.createdAt || null,
|
|
400
|
+
type: "ai-generated-file",
|
|
401
|
+
label: `AI-generated file tracked`,
|
|
402
|
+
detail: `${file.gitPath} (${file.model || "unknown model"}, ~${file.estimatedTokens} tokens)`,
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
for (const file of (cursorData.aiDeletedFiles || []).slice(0, 5)) {
|
|
407
|
+
events.push({
|
|
408
|
+
timestamp: file.deletedAt || null,
|
|
409
|
+
type: "ai-deleted-file",
|
|
410
|
+
label: `AI-generated file deleted`,
|
|
411
|
+
detail: `${file.gitPath} (${file.model || "unknown model"})`,
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return events.sort((a, b) => {
|
|
416
|
+
const ta = a.timestamp ? new Date(a.timestamp).getTime() : 0;
|
|
417
|
+
const tb = b.timestamp ? new Date(b.timestamp).getTime() : 0;
|
|
418
|
+
return ta - tb;
|
|
419
|
+
}).slice(-20);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function buildCursorDiagnosis(cursorData) {
|
|
423
|
+
const drivers = [];
|
|
424
|
+
const recommendations = [];
|
|
425
|
+
|
|
426
|
+
const authorship = cursorData.aiAuthorship || {};
|
|
427
|
+
if (authorship.aiAuthorshipPercent >= 80) {
|
|
428
|
+
drivers.push({
|
|
429
|
+
type: "high-ai-authorship",
|
|
430
|
+
value: `${authorship.aiAuthorshipPercent}%`,
|
|
431
|
+
message: "Most committed code is AI-generated; review coverage may need extra attention.",
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
if (authorship.tabLinesAdded > authorship.composerLinesAdded && authorship.tabLinesAdded > 50) {
|
|
435
|
+
drivers.push({
|
|
436
|
+
type: "tab-completion-heavy",
|
|
437
|
+
value: `${authorship.tabLinesAdded} lines`,
|
|
438
|
+
message: "Tab completions contribute more code than composer/agent sessions.",
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const totalSessions = cursorData.totalSessions || 0;
|
|
443
|
+
const activeSessions = cursorData.activeSessions || 0;
|
|
444
|
+
if (totalSessions > 50 && activeSessions > 40) {
|
|
445
|
+
drivers.push({
|
|
446
|
+
type: "session-accumulation",
|
|
447
|
+
value: `${totalSessions} total, ${activeSessions} active`,
|
|
448
|
+
message: "Many non-archived sessions; old context may accumulate.",
|
|
449
|
+
});
|
|
450
|
+
recommendations.push("Archive old Cursor composer sessions to reduce context clutter.");
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const deletedCount = (cursorData.aiDeletedFiles || []).length;
|
|
454
|
+
const generatedCount = (cursorData.aiGeneratedFiles || []).length;
|
|
455
|
+
if (deletedCount > 3) {
|
|
456
|
+
drivers.push({
|
|
457
|
+
type: "ai-churn",
|
|
458
|
+
value: `${deletedCount} files deleted`,
|
|
459
|
+
message: "AI-generated files are being created and deleted, suggesting trial-and-error patterns.",
|
|
460
|
+
});
|
|
461
|
+
recommendations.push("Use context packs and firewall rules to scope AI tasks more precisely.");
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (generatedCount > 0) {
|
|
465
|
+
recommendations.push("Review AI-generated tracked files — some may be leaking into context.");
|
|
466
|
+
}
|
|
467
|
+
if (authorship.totalCommits > 0) {
|
|
468
|
+
recommendations.push("Use npx getprismo cursor authorship to track AI vs human code ratios over time.");
|
|
469
|
+
}
|
|
470
|
+
if (!recommendations.length) {
|
|
471
|
+
recommendations.push("npx getprismo doctor to optimize repo for Cursor sessions.");
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return {
|
|
475
|
+
drivers: drivers.slice(0, 5),
|
|
476
|
+
recommendations: Array.from(new Set(recommendations)).slice(0, 4),
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function renderCursorTerminal(cursorData, command) {
|
|
481
|
+
const lines = [];
|
|
482
|
+
lines.push("");
|
|
483
|
+
lines.push("Prismo Cursor Sessions");
|
|
484
|
+
lines.push("");
|
|
485
|
+
|
|
486
|
+
if (!cursorData.dbAvailable) {
|
|
487
|
+
lines.push("No Cursor AI tracking database found.");
|
|
488
|
+
lines.push("Cursor stores tracking data at ~/.cursor/ai-tracking/ai-code-tracking.db");
|
|
489
|
+
lines.push("Make sure Cursor is installed and has been used at least once.");
|
|
490
|
+
return lines.join("\n");
|
|
491
|
+
}
|
|
492
|
+
if (!cursorData.sqlite3Available) {
|
|
493
|
+
lines.push("sqlite3 command not found. Install sqlite3 to read Cursor tracking data.");
|
|
494
|
+
return lines.join("\n");
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if (command === "list") {
|
|
498
|
+
lines.push(`Total sessions: ${cursorData.totalSessions} (${cursorData.activeSessions} active)`);
|
|
499
|
+
lines.push(`Mode: ${Object.entries(cursorData.modeDistribution).map(([k, v]) => `${k}: ${v}`).join(", ") || "unknown"}`);
|
|
500
|
+
lines.push("");
|
|
501
|
+
const sessions = cursorData.sessions.slice(0, 15);
|
|
502
|
+
sessions.forEach((session, i) => {
|
|
503
|
+
const title = session.title ? ` "${session.title.slice(0, 50)}"` : "";
|
|
504
|
+
const model = session.model ? ` ${session.model}` : "";
|
|
505
|
+
lines.push(`${i + 1}. [${session.mode || "?"}]${title}${model}`);
|
|
506
|
+
lines.push(` +${session.linesAdded}/-${session.linesRemoved} ${session.createdAt || "unknown date"}${session.isArchived ? " (archived)" : ""}`);
|
|
507
|
+
});
|
|
508
|
+
return lines.join("\n");
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (command === "authorship") {
|
|
512
|
+
const auth = cursorData.aiAuthorship;
|
|
513
|
+
lines.push("AI Authorship (from Cursor scored commits)");
|
|
514
|
+
lines.push("");
|
|
515
|
+
lines.push(`Commits analyzed: ${auth.totalCommits}`);
|
|
516
|
+
lines.push(`Total lines added: ${auth.totalLinesAdded}`);
|
|
517
|
+
lines.push("");
|
|
518
|
+
lines.push(` Composer (agent): ${auth.composerLinesAdded} lines`);
|
|
519
|
+
lines.push(` Tab completions: ${auth.tabLinesAdded} lines`);
|
|
520
|
+
lines.push(` Human: ${auth.humanLinesAdded} lines`);
|
|
521
|
+
lines.push("--------------------------------------------------");
|
|
522
|
+
lines.push(` AI authorship: ${auth.aiAuthorshipPercent}%`);
|
|
523
|
+
lines.push("");
|
|
524
|
+
if (cursorData.scoredCommits.length) {
|
|
525
|
+
lines.push("Recent commits:");
|
|
526
|
+
cursorData.scoredCommits.slice(0, 8).forEach((c) => {
|
|
527
|
+
const aiLines = (c.composerLinesAdded || 0) + (c.tabLinesAdded || 0);
|
|
528
|
+
const pct = c.linesAdded > 0 ? Math.round((aiLines / c.linesAdded) * 100) : 0;
|
|
529
|
+
const msg = (c.commitMessage || c.commitHash || "").slice(0, 50);
|
|
530
|
+
lines.push(` ${pct}% AI +${c.linesAdded}/-${c.linesDeleted} ${msg}`);
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
return lines.join("\n");
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
if (command === "timeline") {
|
|
537
|
+
const timeline = buildCursorSessionTimeline(cursorData);
|
|
538
|
+
lines.push("Cursor Session Timeline");
|
|
539
|
+
lines.push("");
|
|
540
|
+
if (!timeline.length) {
|
|
541
|
+
lines.push("No timeline events found. Cursor needs scored commits or tracked files for timeline data.");
|
|
542
|
+
} else {
|
|
543
|
+
timeline.forEach((event) => {
|
|
544
|
+
const when = event.timestamp ? new Date(event.timestamp).toLocaleString() : "unknown";
|
|
545
|
+
lines.push(`${when} [${event.type}]`);
|
|
546
|
+
lines.push(` ${event.label}`);
|
|
547
|
+
lines.push(` ${event.detail}`);
|
|
548
|
+
lines.push("");
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
const diagnosis = buildCursorDiagnosis(cursorData);
|
|
552
|
+
if (diagnosis.drivers.length) {
|
|
553
|
+
lines.push("Signals:");
|
|
554
|
+
diagnosis.drivers.forEach((d) => lines.push(`- ${d.message}`));
|
|
555
|
+
}
|
|
556
|
+
lines.push("");
|
|
557
|
+
lines.push("Suggested Actions:");
|
|
558
|
+
diagnosis.recommendations.forEach((r) => lines.push(`- ${r}`));
|
|
559
|
+
return lines.join("\n");
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
if (command === "files") {
|
|
563
|
+
lines.push("AI-Generated Files (tracked by Cursor)");
|
|
564
|
+
lines.push("");
|
|
565
|
+
if (!cursorData.aiGeneratedFiles.length && !cursorData.aiDeletedFiles.length) {
|
|
566
|
+
lines.push("No AI-generated files tracked yet.");
|
|
567
|
+
return lines.join("\n");
|
|
568
|
+
}
|
|
569
|
+
if (cursorData.aiGeneratedFiles.length) {
|
|
570
|
+
lines.push(`Tracked: ${cursorData.aiGeneratedFiles.length}`);
|
|
571
|
+
cursorData.aiGeneratedFiles.forEach((f) => {
|
|
572
|
+
lines.push(` ${f.gitPath} (${f.model || "?"}, ~${f.estimatedTokens} tokens)`);
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
if (cursorData.aiDeletedFiles.length) {
|
|
576
|
+
lines.push("");
|
|
577
|
+
lines.push(`Deleted: ${cursorData.aiDeletedFiles.length}`);
|
|
578
|
+
cursorData.aiDeletedFiles.forEach((f) => {
|
|
579
|
+
lines.push(` ${f.gitPath} (${f.model || "?"}, ${f.deletedAt || "?"})`);
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
return lines.join("\n");
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Default: summary view
|
|
586
|
+
lines.push(`Sessions: ${cursorData.totalSessions} total (${cursorData.activeSessions} active)`);
|
|
587
|
+
lines.push(`Modes: ${Object.entries(cursorData.modeDistribution).map(([k, v]) => `${k}: ${v}`).join(", ") || "none"}`);
|
|
588
|
+
if (Object.keys(cursorData.modelDistribution).length) {
|
|
589
|
+
lines.push(`Models: ${Object.entries(cursorData.modelDistribution).map(([k, v]) => `${k}: ${v}`).join(", ")}`);
|
|
590
|
+
}
|
|
591
|
+
lines.push("");
|
|
592
|
+
const auth = cursorData.aiAuthorship;
|
|
593
|
+
if (auth.totalCommits > 0) {
|
|
594
|
+
lines.push("AI Authorship");
|
|
595
|
+
lines.push(` ${auth.totalCommits} commits analyzed, ${auth.aiAuthorshipPercent}% AI-authored`);
|
|
596
|
+
lines.push(` Composer: ${auth.composerLinesAdded} Tab: ${auth.tabLinesAdded} Human: ${auth.humanLinesAdded} lines`);
|
|
597
|
+
} else {
|
|
598
|
+
lines.push("AI Authorship: no scored commits found yet");
|
|
599
|
+
}
|
|
600
|
+
lines.push("");
|
|
601
|
+
|
|
602
|
+
if (cursorData.aiGeneratedFiles.length) {
|
|
603
|
+
lines.push(`AI-generated files: ${cursorData.aiGeneratedFiles.length} tracked`);
|
|
604
|
+
}
|
|
605
|
+
if (cursorData.aiDeletedFiles.length) {
|
|
606
|
+
lines.push(`AI-deleted files: ${cursorData.aiDeletedFiles.length} (churn)`);
|
|
607
|
+
}
|
|
608
|
+
if (cursorData.dbStats) {
|
|
609
|
+
lines.push("");
|
|
610
|
+
lines.push("Tracking DB:");
|
|
611
|
+
lines.push(` Code hashes: ${cursorData.dbStats.ai_code_hashes} Conversations: ${cursorData.dbStats.conversation_summaries} Commits: ${cursorData.dbStats.scored_commits}`);
|
|
612
|
+
}
|
|
613
|
+
lines.push("");
|
|
614
|
+
const diagnosis = buildCursorDiagnosis(cursorData);
|
|
615
|
+
if (diagnosis.drivers.length) {
|
|
616
|
+
lines.push("Signals:");
|
|
617
|
+
diagnosis.drivers.forEach((d) => lines.push(`- ${d.message}`));
|
|
618
|
+
lines.push("");
|
|
619
|
+
}
|
|
620
|
+
lines.push("Next:");
|
|
621
|
+
diagnosis.recommendations.slice(0, 3).forEach((r) => lines.push(`- ${r}`));
|
|
622
|
+
return lines.join("\n");
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
return {
|
|
626
|
+
analyzeCursorSessions,
|
|
627
|
+
buildCursorDiagnosis,
|
|
628
|
+
buildCursorSessionTimeline,
|
|
629
|
+
getAiTrackingDbStats,
|
|
630
|
+
getCursorAiCodeHashes,
|
|
631
|
+
getCursorComposerHeaders,
|
|
632
|
+
getCursorConversationSummaries,
|
|
633
|
+
getCursorDeletedFiles,
|
|
634
|
+
getCursorIdeState,
|
|
635
|
+
getCursorScoredCommits,
|
|
636
|
+
getCursorTrackedFileContent,
|
|
637
|
+
getCursorWorkspaceForProject,
|
|
638
|
+
isSqlite3Available,
|
|
639
|
+
renderCursorTerminal,
|
|
640
|
+
};
|
|
641
|
+
};
|
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
|
@@ -20,6 +20,7 @@ module.exports = function createScan(deps) {
|
|
|
20
20
|
getCodexSessionFiles,
|
|
21
21
|
compactUsageSummary,
|
|
22
22
|
formatTokenCount,
|
|
23
|
+
color,
|
|
23
24
|
} = deps;
|
|
24
25
|
|
|
25
26
|
const {
|
|
@@ -229,7 +230,11 @@ function renderSetupTerminal(result) {
|
|
|
229
230
|
lines.push("Detected:");
|
|
230
231
|
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
232
|
lines.push(`- Codex: ${result.detected.codex.detected ? "detected" : "not detected"}; logs: ${result.detected.codex.localLogsFound ? "found" : "not found"}; MCP: ${result.detected.codex.mcpServers}`);
|
|
232
|
-
|
|
233
|
+
const cursorInfo = result.detected.cursor;
|
|
234
|
+
lines.push(`- Cursor: ${cursorInfo.detected ? "detected" : "not detected"}${cursorInfo.dbAvailable ? `; tracking DB: found (${cursorInfo.totalSessions} sessions, ${cursorInfo.activeSessions} active)` : ""}${cursorInfo.workspace ? `; workspace: matched` : ""}`);
|
|
235
|
+
if (cursorInfo.dbStats) {
|
|
236
|
+
lines.push(` Tracking: ${cursorInfo.dbStats.scored_commits} scored commits, ${cursorInfo.dbStats.ai_code_hashes} code hashes, ${cursorInfo.dbStats.conversation_summaries} conversation summaries`);
|
|
237
|
+
}
|
|
233
238
|
const detectedTools = result.detected.optimizationStack.detectedTools;
|
|
234
239
|
lines.push(`- Optimization tools: ${detectedTools.length ? detectedTools.join(", ") : "none detected"}`);
|
|
235
240
|
lines.push("");
|
|
@@ -562,6 +567,31 @@ function scanRepo(rootDir = process.cwd(), options = {}) {
|
|
|
562
567
|
}
|
|
563
568
|
const optimizationStack = detectOptimizationStack(root, claudeConfig, codexConfig);
|
|
564
569
|
const agentReadiness = detectAgentReadiness(root, claudeConfig, codexConfig, realUsage);
|
|
570
|
+
|
|
571
|
+
if (agentReadiness.cursor.dbAvailable) {
|
|
572
|
+
try {
|
|
573
|
+
const cursorMod = require("./cursor-sessions")({ fs, os, path, estimateTokens });
|
|
574
|
+
const tracked = cursorMod.getCursorTrackedFileContent(20);
|
|
575
|
+
const cursorAiFiles = tracked.filter((f) => {
|
|
576
|
+
const fullPath = path.join(root, f.gitPath);
|
|
577
|
+
return fs.existsSync(fullPath);
|
|
578
|
+
});
|
|
579
|
+
if (cursorAiFiles.length >= 3) {
|
|
580
|
+
addIssue(
|
|
581
|
+
issues,
|
|
582
|
+
"medium",
|
|
583
|
+
"ai_generated_files",
|
|
584
|
+
`${cursorAiFiles.length} AI-generated files still in repo (tracked by Cursor)`,
|
|
585
|
+
cursorAiFiles.slice(0, 5).map((f) => `${f.gitPath} (${f.model || "unknown model"})`).join(", "),
|
|
586
|
+
"Review AI-generated files for quality and whether they should remain in the project.",
|
|
587
|
+
`AI-generated content may inflate context if agents re-read it. Run: npx getprismo cursor files`
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
} catch {
|
|
591
|
+
// Cursor data unavailable
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
565
595
|
const toolOutputRisk = detectToolOutputRisk({ exposedLargeFiles, exposedHighRiskDirs, highRiskDirs });
|
|
566
596
|
const operationalNoise = detectOperationalNoise(files);
|
|
567
597
|
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,
|
package/lib/prismo-dev-scan.js
CHANGED
|
@@ -53,6 +53,9 @@ const {
|
|
|
53
53
|
|
|
54
54
|
const {
|
|
55
55
|
analyzeSessionFile,
|
|
56
|
+
analyzeCursorSessions,
|
|
57
|
+
buildCursorDiagnosis,
|
|
58
|
+
buildCursorSessionTimeline,
|
|
56
59
|
calculateClaudeCost,
|
|
57
60
|
compactUsageSummary,
|
|
58
61
|
formatMoney,
|
|
@@ -60,11 +63,13 @@ const {
|
|
|
60
63
|
getClaudeCodeCostSummary,
|
|
61
64
|
getClaudeSessionFiles,
|
|
62
65
|
getCodexSessionFiles,
|
|
66
|
+
getCursorSessionSummary,
|
|
63
67
|
getUsageSummary,
|
|
64
68
|
getPositionals,
|
|
65
69
|
parsePositiveInt,
|
|
66
70
|
parseScopeAndTarget,
|
|
67
71
|
renderClaudeCostTerminal,
|
|
72
|
+
renderCursorTerminal,
|
|
68
73
|
renderUsageTerminal,
|
|
69
74
|
renderContextThrottle,
|
|
70
75
|
renderRescuePrompt,
|
|
@@ -112,6 +117,7 @@ const scanApi = require("./prismo-dev/scan")({
|
|
|
112
117
|
getCodexSessionFiles,
|
|
113
118
|
compactUsageSummary,
|
|
114
119
|
formatTokenCount,
|
|
120
|
+
color,
|
|
115
121
|
});
|
|
116
122
|
|
|
117
123
|
({ scanRepo } = scanApi);
|
|
@@ -265,8 +271,9 @@ Usage:
|
|
|
265
271
|
prismo optimize [scope] [--json] [path]
|
|
266
272
|
prismo context [scope] [--json] [path]
|
|
267
273
|
prismo cc [list|last N|all|timeline] [--json] [--limit N] [--firewall] [--task TASK] [path]
|
|
268
|
-
prismo
|
|
269
|
-
prismo
|
|
274
|
+
prismo cursor [list|authorship|timeline|files|all] [--json] [--limit N] [path]
|
|
275
|
+
prismo usage [codex|claude|cursor|all] [--json] [--limit N] [path]
|
|
276
|
+
prismo watch [codex|claude|cursor|all] [--json] [--once] [--agents] [--report] [--rescue] [--guardrails] [--throttle] [--events] [--no-events] [--auto] [--budget N] [--redact-paths] [--interval N] [path]
|
|
270
277
|
prismo demo
|
|
271
278
|
|
|
272
279
|
Commands:
|
|
@@ -281,7 +288,8 @@ Commands:
|
|
|
281
288
|
optimize Generate lightweight AI-readable project context files in .prismo/.
|
|
282
289
|
context Print a copy-pasteable compact context prompt for AI coding tools.
|
|
283
290
|
cc Show Claude Code token cost, cache cost, and all-time totals.
|
|
284
|
-
|
|
291
|
+
cursor Show Cursor session data, AI authorship, and AI-generated file tracking.
|
|
292
|
+
usage Read local Codex/Claude Code/Cursor session logs and summarize token usage.
|
|
285
293
|
watch Refresh local session usage in the terminal.
|
|
286
294
|
demo Show sample output without needing a messy repo.
|
|
287
295
|
setup Detect coding tools, tracking modes, local logs, and Prismo proxy readiness.
|
|
@@ -399,22 +407,45 @@ Examples:
|
|
|
399
407
|
timeline shows context spikes, repeated commands, artifact leaks, and tool-output pressure for the latest session.
|
|
400
408
|
--firewall writes .prismo/timeline-firewall-suggestions.md and suggested allow/block files from timeline evidence.
|
|
401
409
|
Without a path, cc commands read all Claude Code projects. Passing a path filters to that project.`,
|
|
410
|
+
cursor: `Prismo Cursor Sessions
|
|
411
|
+
|
|
412
|
+
Usage:
|
|
413
|
+
prismo cursor [--json] [--limit N] [path]
|
|
414
|
+
prismo cursor list [--json] [--limit N] [path]
|
|
415
|
+
prismo cursor authorship [--json] [--limit N] [path]
|
|
416
|
+
prismo cursor timeline [--json] [--limit N] [path]
|
|
417
|
+
prismo cursor files [--json] [--limit N] [path]
|
|
418
|
+
prismo cursor all [--json] [--limit N] [path]
|
|
419
|
+
|
|
420
|
+
Examples:
|
|
421
|
+
prismo cursor
|
|
422
|
+
prismo cursor authorship
|
|
423
|
+
prismo cursor list --limit 20
|
|
424
|
+
prismo cursor timeline --json
|
|
425
|
+
prismo cursor files
|
|
426
|
+
|
|
427
|
+
Output:
|
|
428
|
+
Reads ~/.cursor/ai-tracking/ai-code-tracking.db and Cursor workspace state databases.
|
|
429
|
+
Shows Cursor composer sessions, AI authorship percentages from scored commits, AI-generated file tracking, and churn analysis.
|
|
430
|
+
Requires sqlite3 to be installed on the system.`,
|
|
402
431
|
usage: `Prismo Usage
|
|
403
432
|
|
|
404
433
|
Usage:
|
|
405
|
-
prismo usage [codex|claude|all] [--json] [--limit N] [path]
|
|
434
|
+
prismo usage [codex|claude|cursor|all] [--json] [--limit N] [path]
|
|
406
435
|
|
|
407
436
|
Examples:
|
|
408
437
|
prismo usage
|
|
409
438
|
prismo usage codex --json
|
|
439
|
+
prismo usage cursor
|
|
410
440
|
prismo usage claude --limit 3`,
|
|
411
441
|
watch: `Prismo Watch
|
|
412
442
|
|
|
413
443
|
Usage:
|
|
414
|
-
prismo watch [codex|claude|all] [--json] [--once] [--agents] [--report] [--rescue] [--guardrails] [--throttle] [--events] [--no-events] [--auto] [--budget N] [--redact-paths] [--interval N] [path]
|
|
444
|
+
prismo watch [codex|claude|cursor|all] [--json] [--once] [--agents] [--report] [--rescue] [--guardrails] [--throttle] [--events] [--no-events] [--auto] [--budget N] [--redact-paths] [--interval N] [path]
|
|
415
445
|
|
|
416
446
|
Examples:
|
|
417
447
|
prismo watch codex
|
|
448
|
+
prismo watch cursor --once --json
|
|
418
449
|
prismo watch claude --once --json
|
|
419
450
|
prismo watch --agents --once
|
|
420
451
|
prismo watch --once --report
|
|
@@ -519,6 +550,7 @@ Tools exposed:
|
|
|
519
550
|
prismo_context_pack
|
|
520
551
|
prismo_firewall
|
|
521
552
|
prismo_cc_timeline
|
|
553
|
+
prismo_cursor_sessions
|
|
522
554
|
|
|
523
555
|
Output:
|
|
524
556
|
Starts a local JSON-RPC MCP server over stdio. Use it from MCP-compatible clients so agents can scan context waste, search shielded command output, and request scoped context without loading huge logs into chat.
|
|
@@ -574,8 +606,8 @@ async function runCli(argv) {
|
|
|
574
606
|
printCommandHelp(command);
|
|
575
607
|
return;
|
|
576
608
|
}
|
|
577
|
-
if (!["dev", "init", "doctor", "firewall", "benchmark", "shield", "mcp", "setup", "scan", "optimize", "context", "cc", "usage", "watch", "demo"].includes(command)) {
|
|
578
|
-
throw new Error(`Unknown command: ${command}. Try: prismo doctor, prismo watch, prismo benchmark, prismo shield, prismo mcp, prismo firewall, prismo init, prismo scan, prismo optimize, prismo context, prismo cc, or prismo usage`);
|
|
609
|
+
if (!["dev", "init", "doctor", "firewall", "benchmark", "shield", "mcp", "setup", "scan", "optimize", "context", "cc", "cursor", "usage", "watch", "demo"].includes(command)) {
|
|
610
|
+
throw new Error(`Unknown command: ${command}. Try: prismo doctor, prismo watch, prismo benchmark, prismo shield, prismo mcp, prismo firewall, prismo init, prismo scan, prismo optimize, prismo context, prismo cc, prismo cursor, or prismo usage`);
|
|
579
611
|
}
|
|
580
612
|
|
|
581
613
|
if (command === "demo") {
|
|
@@ -725,6 +757,7 @@ async function runCli(argv) {
|
|
|
725
757
|
toDoctorJsonPayload,
|
|
726
758
|
getUsageSummary,
|
|
727
759
|
getClaudeCodeCostSummary,
|
|
760
|
+
getCursorSessionSummary,
|
|
728
761
|
runOptimize,
|
|
729
762
|
createOptimizeContext,
|
|
730
763
|
renderStarterPrompt,
|
|
@@ -839,9 +872,31 @@ async function runCli(argv) {
|
|
|
839
872
|
return;
|
|
840
873
|
}
|
|
841
874
|
|
|
875
|
+
if (command === "cursor") {
|
|
876
|
+
const json = rest.includes("--json");
|
|
877
|
+
const limitIndex = rest.indexOf("--limit");
|
|
878
|
+
const positional = getPositionals(rest, new Set(["--limit"]));
|
|
879
|
+
const subcommand = positional[0] && ["list", "authorship", "timeline", "files", "all"].includes(positional[0].toLowerCase())
|
|
880
|
+
? positional[0].toLowerCase()
|
|
881
|
+
: "latest";
|
|
882
|
+
const target = (subcommand !== "latest" ? positional[1] : positional[0]) || process.cwd();
|
|
883
|
+
const limit = parsePositiveInt(limitIndex >= 0 ? rest[limitIndex + 1] : null, subcommand === "all" ? 200 : 20);
|
|
884
|
+
const summary = getCursorSessionSummary({
|
|
885
|
+
cwd: path.resolve(target),
|
|
886
|
+
limit,
|
|
887
|
+
mode: subcommand,
|
|
888
|
+
});
|
|
889
|
+
if (json) {
|
|
890
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
console.log(renderCursorTerminal(summary, subcommand));
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
|
|
842
897
|
if (command === "usage" || command === "watch") {
|
|
843
898
|
const json = rest.includes("--json");
|
|
844
|
-
const knownTools = new Set(["codex", "claude", "all"]);
|
|
899
|
+
const knownTools = new Set(["codex", "claude", "cursor", "all"]);
|
|
845
900
|
const positional = getPositionals(rest, new Set(["--limit", "--interval", "--budget"]));
|
|
846
901
|
const explicitTool = positional[0] && knownTools.has(positional[0].toLowerCase());
|
|
847
902
|
const tool = explicitTool ? positional[0].toLowerCase() : "all";
|
|
@@ -1021,6 +1076,7 @@ module.exports = {
|
|
|
1021
1076
|
renderMarkdownReport,
|
|
1022
1077
|
renderSimpleScanReport,
|
|
1023
1078
|
renderClaudeCostTerminal,
|
|
1079
|
+
renderCursorTerminal,
|
|
1024
1080
|
renderUsageTerminal,
|
|
1025
1081
|
renderContextThrottle,
|
|
1026
1082
|
renderRescuePrompt,
|
|
@@ -1040,7 +1096,9 @@ module.exports = {
|
|
|
1040
1096
|
runCli,
|
|
1041
1097
|
scanRepo,
|
|
1042
1098
|
getClaudeCodeCostSummary,
|
|
1099
|
+
getCursorSessionSummary,
|
|
1043
1100
|
getUsageSummary,
|
|
1101
|
+
analyzeCursorSessions,
|
|
1044
1102
|
analyzeSessionFile,
|
|
1045
1103
|
calculateClaudeCost,
|
|
1046
1104
|
toDoctorJsonPayload,
|
package/package.json
CHANGED