@vemdev/mcp-server 0.1.1
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/LICENSE +21 -0
- package/README.md +31 -0
- package/dist/chunk-52K33N7B.js +183 -0
- package/dist/chunk-52K33N7B.js.map +1 -0
- package/dist/chunk-CSA73CBK.js +181 -0
- package/dist/chunk-CSA73CBK.js.map +1 -0
- package/dist/chunk-WYWBLQNM.js +170 -0
- package/dist/chunk-WYWBLQNM.js.map +1 -0
- package/dist/claude-sessions-5HEECZ63.js +11 -0
- package/dist/claude-sessions-5HEECZ63.js.map +1 -0
- package/dist/copilot-sessions-LLDNCHIU.js +13 -0
- package/dist/copilot-sessions-LLDNCHIU.js.map +1 -0
- package/dist/gemini-sessions-RPV25JO4.js +11 -0
- package/dist/gemini-sessions-RPV25JO4.js.map +1 -0
- package/dist/index.js +3744 -0
- package/dist/index.js.map +1 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 dnotech
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# @vemdev/mcp-server
|
|
2
|
+
|
|
3
|
+
Official [VEM](https://vem.dev) MCP Server for AI agent project memory.
|
|
4
|
+
|
|
5
|
+
Exposes project memory tools (tasks, decisions, context) to AI agents via the [Model Context Protocol](https://modelcontextprotocol.io).
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
Run the server directly:
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npx @vemdev/mcp-server@latest
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Or configure it in your MCP host (e.g. Claude Desktop, Cursor):
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"mcpServers": {
|
|
20
|
+
"vem": {
|
|
21
|
+
"command": "npx",
|
|
22
|
+
"args": ["@vemdev/mcp-server@latest"]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
## License
|
|
30
|
+
|
|
31
|
+
MIT
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// ../../packages/core/dist/copilot-sessions.js
|
|
2
|
+
import { readdir, readFile } from "fs/promises";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
function getCopilotSessionsDir() {
|
|
6
|
+
return join(homedir(), ".copilot", "session-state");
|
|
7
|
+
}
|
|
8
|
+
async function parseWorkspaceYaml(content) {
|
|
9
|
+
const result = {};
|
|
10
|
+
for (const line of content.split("\n")) {
|
|
11
|
+
const colonIdx = line.indexOf(":");
|
|
12
|
+
if (colonIdx === -1)
|
|
13
|
+
continue;
|
|
14
|
+
const key = line.slice(0, colonIdx).trim();
|
|
15
|
+
const value = line.slice(colonIdx + 1).trim();
|
|
16
|
+
if (key && value)
|
|
17
|
+
result[key] = value;
|
|
18
|
+
}
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
async function listCopilotSessions(gitRoot) {
|
|
22
|
+
const sessionsDir = getCopilotSessionsDir();
|
|
23
|
+
let entries;
|
|
24
|
+
try {
|
|
25
|
+
entries = await readdir(sessionsDir);
|
|
26
|
+
} catch {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
const sessions = [];
|
|
30
|
+
for (const entry of entries) {
|
|
31
|
+
if (entry.includes("."))
|
|
32
|
+
continue;
|
|
33
|
+
const workspacePath = join(sessionsDir, entry, "workspace.yaml");
|
|
34
|
+
try {
|
|
35
|
+
const content = await readFile(workspacePath, "utf-8");
|
|
36
|
+
const parsed = await parseWorkspaceYaml(content);
|
|
37
|
+
if (!parsed.id)
|
|
38
|
+
continue;
|
|
39
|
+
if (gitRoot && parsed.git_root && parsed.git_root !== gitRoot)
|
|
40
|
+
continue;
|
|
41
|
+
sessions.push({
|
|
42
|
+
id: parsed.id,
|
|
43
|
+
summary: parsed.summary || "",
|
|
44
|
+
branch: parsed.branch || null,
|
|
45
|
+
repository: parsed.repository || null,
|
|
46
|
+
git_root: parsed.git_root || null,
|
|
47
|
+
cwd: parsed.cwd || null,
|
|
48
|
+
created_at: parsed.created_at || "",
|
|
49
|
+
updated_at: parsed.updated_at || ""
|
|
50
|
+
});
|
|
51
|
+
} catch {
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
sessions.sort((a, b) => {
|
|
55
|
+
if (!a.updated_at)
|
|
56
|
+
return 1;
|
|
57
|
+
if (!b.updated_at)
|
|
58
|
+
return -1;
|
|
59
|
+
return b.updated_at.localeCompare(a.updated_at);
|
|
60
|
+
});
|
|
61
|
+
return sessions;
|
|
62
|
+
}
|
|
63
|
+
async function readCopilotSessionDetail(id) {
|
|
64
|
+
const sessionsDir = getCopilotSessionsDir();
|
|
65
|
+
const workspacePath = join(sessionsDir, id, "workspace.yaml");
|
|
66
|
+
const eventsPath = join(sessionsDir, id, "events.jsonl");
|
|
67
|
+
let session;
|
|
68
|
+
try {
|
|
69
|
+
const content = await readFile(workspacePath, "utf-8");
|
|
70
|
+
const parsed = await parseWorkspaceYaml(content);
|
|
71
|
+
if (!parsed.id)
|
|
72
|
+
return null;
|
|
73
|
+
session = {
|
|
74
|
+
id: parsed.id,
|
|
75
|
+
summary: parsed.summary || "",
|
|
76
|
+
branch: parsed.branch || null,
|
|
77
|
+
repository: parsed.repository || null,
|
|
78
|
+
git_root: parsed.git_root || null,
|
|
79
|
+
cwd: parsed.cwd || null,
|
|
80
|
+
created_at: parsed.created_at || "",
|
|
81
|
+
updated_at: parsed.updated_at || ""
|
|
82
|
+
};
|
|
83
|
+
} catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
const intents = [];
|
|
87
|
+
const user_messages = [];
|
|
88
|
+
try {
|
|
89
|
+
const eventsContent = await readFile(eventsPath, "utf-8");
|
|
90
|
+
for (const line of eventsContent.split("\n")) {
|
|
91
|
+
if (!line.trim())
|
|
92
|
+
continue;
|
|
93
|
+
let event;
|
|
94
|
+
try {
|
|
95
|
+
event = JSON.parse(line);
|
|
96
|
+
} catch {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const type = event.type;
|
|
100
|
+
const data = event.data;
|
|
101
|
+
if (type === "user.message" && typeof data?.content === "string") {
|
|
102
|
+
const msg = data.content.trim();
|
|
103
|
+
if (msg)
|
|
104
|
+
user_messages.push(msg);
|
|
105
|
+
}
|
|
106
|
+
if (type === "assistant.message") {
|
|
107
|
+
const toolRequests = data?.toolRequests;
|
|
108
|
+
if (Array.isArray(toolRequests)) {
|
|
109
|
+
for (const req of toolRequests) {
|
|
110
|
+
if (req.name === "report_intent" && typeof req.arguments?.intent === "string") {
|
|
111
|
+
const intent = req.arguments.intent.trim();
|
|
112
|
+
if (intent)
|
|
113
|
+
intents.push(intent);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
} catch {
|
|
120
|
+
}
|
|
121
|
+
return { ...session, intents, user_messages };
|
|
122
|
+
}
|
|
123
|
+
async function computeCopilotSessionStats(id) {
|
|
124
|
+
const sessionsDir = getCopilotSessionsDir();
|
|
125
|
+
const eventsPath = join(sessionsDir, id, "events.jsonl");
|
|
126
|
+
let content;
|
|
127
|
+
try {
|
|
128
|
+
content = await readFile(eventsPath, "utf-8");
|
|
129
|
+
} catch {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
let tsFirst = null;
|
|
133
|
+
let tsLast = null;
|
|
134
|
+
let turnCount = 0;
|
|
135
|
+
let toolCallCount = 0;
|
|
136
|
+
const modelBreakdown = {};
|
|
137
|
+
for (const line of content.split("\n")) {
|
|
138
|
+
if (!line.trim())
|
|
139
|
+
continue;
|
|
140
|
+
let event;
|
|
141
|
+
try {
|
|
142
|
+
event = JSON.parse(line);
|
|
143
|
+
} catch {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
const ts = event.timestamp;
|
|
147
|
+
if (ts) {
|
|
148
|
+
if (!tsFirst)
|
|
149
|
+
tsFirst = ts;
|
|
150
|
+
tsLast = ts;
|
|
151
|
+
}
|
|
152
|
+
const type = event.type;
|
|
153
|
+
if (type === "assistant.turn_end") {
|
|
154
|
+
turnCount++;
|
|
155
|
+
}
|
|
156
|
+
if (type === "tool.execution_complete") {
|
|
157
|
+
toolCallCount++;
|
|
158
|
+
const data = event.data;
|
|
159
|
+
const model = data?.model;
|
|
160
|
+
if (model) {
|
|
161
|
+
modelBreakdown[model] = (modelBreakdown[model] ?? 0) + 1;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (!tsFirst)
|
|
166
|
+
return null;
|
|
167
|
+
const sessionDurationMs = tsLast && tsFirst ? new Date(tsLast).getTime() - new Date(tsFirst).getTime() : void 0;
|
|
168
|
+
return {
|
|
169
|
+
ended_at: tsLast ?? void 0,
|
|
170
|
+
session_duration_ms: sessionDurationMs,
|
|
171
|
+
turn_count: turnCount || void 0,
|
|
172
|
+
tool_call_count: toolCallCount || void 0,
|
|
173
|
+
model_breakdown: Object.keys(modelBreakdown).length > 0 ? modelBreakdown : void 0
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export {
|
|
178
|
+
getCopilotSessionsDir,
|
|
179
|
+
listCopilotSessions,
|
|
180
|
+
readCopilotSessionDetail,
|
|
181
|
+
computeCopilotSessionStats
|
|
182
|
+
};
|
|
183
|
+
//# sourceMappingURL=chunk-52K33N7B.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../packages/core/dist/copilot-sessions.js"],"sourcesContent":["import { readdir, readFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nexport function getCopilotSessionsDir() {\n return join(homedir(), \".copilot\", \"session-state\");\n}\nasync function parseWorkspaceYaml(content) {\n const result = {};\n for (const line of content.split(\"\\n\")) {\n const colonIdx = line.indexOf(\":\");\n if (colonIdx === -1)\n continue;\n const key = line.slice(0, colonIdx).trim();\n const value = line.slice(colonIdx + 1).trim();\n if (key && value)\n result[key] = value;\n }\n return result;\n}\nexport async function listCopilotSessions(gitRoot) {\n const sessionsDir = getCopilotSessionsDir();\n let entries;\n try {\n entries = await readdir(sessionsDir);\n }\n catch {\n return [];\n }\n const sessions = [];\n for (const entry of entries) {\n // Skip .jsonl files at root and non-uuid entries\n if (entry.includes(\".\"))\n continue;\n const workspacePath = join(sessionsDir, entry, \"workspace.yaml\");\n try {\n const content = await readFile(workspacePath, \"utf-8\");\n const parsed = await parseWorkspaceYaml(content);\n if (!parsed.id)\n continue;\n // Filter by git_root if provided\n if (gitRoot && parsed.git_root && parsed.git_root !== gitRoot)\n continue;\n sessions.push({\n id: parsed.id,\n summary: parsed.summary || \"\",\n branch: parsed.branch || null,\n repository: parsed.repository || null,\n git_root: parsed.git_root || null,\n cwd: parsed.cwd || null,\n created_at: parsed.created_at || \"\",\n updated_at: parsed.updated_at || \"\",\n });\n }\n catch {\n // No workspace.yaml or unreadable — skip\n }\n }\n // Sort newest-first by updated_at\n sessions.sort((a, b) => {\n if (!a.updated_at)\n return 1;\n if (!b.updated_at)\n return -1;\n return b.updated_at.localeCompare(a.updated_at);\n });\n return sessions;\n}\nexport async function readCopilotSessionDetail(id) {\n const sessionsDir = getCopilotSessionsDir();\n const workspacePath = join(sessionsDir, id, \"workspace.yaml\");\n const eventsPath = join(sessionsDir, id, \"events.jsonl\");\n let session;\n try {\n const content = await readFile(workspacePath, \"utf-8\");\n const parsed = await parseWorkspaceYaml(content);\n if (!parsed.id)\n return null;\n session = {\n id: parsed.id,\n summary: parsed.summary || \"\",\n branch: parsed.branch || null,\n repository: parsed.repository || null,\n git_root: parsed.git_root || null,\n cwd: parsed.cwd || null,\n created_at: parsed.created_at || \"\",\n updated_at: parsed.updated_at || \"\",\n };\n }\n catch {\n return null;\n }\n const intents = [];\n const user_messages = [];\n try {\n const eventsContent = await readFile(eventsPath, \"utf-8\");\n for (const line of eventsContent.split(\"\\n\")) {\n if (!line.trim())\n continue;\n let event;\n try {\n event = JSON.parse(line);\n }\n catch {\n continue;\n }\n const type = event.type;\n const data = event.data;\n if (type === \"user.message\" && typeof data?.content === \"string\") {\n const msg = data.content.trim();\n if (msg)\n user_messages.push(msg);\n }\n if (type === \"assistant.message\") {\n const toolRequests = data?.toolRequests;\n if (Array.isArray(toolRequests)) {\n for (const req of toolRequests) {\n if (req.name === \"report_intent\" &&\n typeof req.arguments?.intent ===\n \"string\") {\n const intent = req.arguments.intent.trim();\n if (intent)\n intents.push(intent);\n }\n }\n }\n }\n }\n }\n catch {\n // events.jsonl missing or unreadable — return session without events\n }\n return { ...session, intents, user_messages };\n}\n/**\n * Compute statistics for a Copilot session from its events.jsonl file.\n * Returns null if the session cannot be read.\n */\nexport async function computeCopilotSessionStats(id) {\n const sessionsDir = getCopilotSessionsDir();\n const eventsPath = join(sessionsDir, id, \"events.jsonl\");\n let content;\n try {\n content = await readFile(eventsPath, \"utf-8\");\n }\n catch {\n return null;\n }\n let tsFirst = null;\n let tsLast = null;\n let turnCount = 0;\n let toolCallCount = 0;\n const modelBreakdown = {};\n for (const line of content.split(\"\\n\")) {\n if (!line.trim())\n continue;\n let event;\n try {\n event = JSON.parse(line);\n }\n catch {\n continue;\n }\n const ts = event.timestamp;\n if (ts) {\n if (!tsFirst)\n tsFirst = ts;\n tsLast = ts;\n }\n const type = event.type;\n if (type === \"assistant.turn_end\") {\n turnCount++;\n }\n if (type === \"tool.execution_complete\") {\n toolCallCount++;\n const data = event.data;\n const model = data?.model;\n if (model) {\n modelBreakdown[model] = (modelBreakdown[model] ?? 0) + 1;\n }\n }\n }\n if (!tsFirst)\n return null;\n const sessionDurationMs = tsLast && tsFirst\n ? new Date(tsLast).getTime() - new Date(tsFirst).getTime()\n : undefined;\n return {\n ended_at: tsLast ?? undefined,\n session_duration_ms: sessionDurationMs,\n turn_count: turnCount || undefined,\n tool_call_count: toolCallCount || undefined,\n model_breakdown: Object.keys(modelBreakdown).length > 0 ? modelBreakdown : undefined,\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
// ../../packages/core/dist/claude-sessions.js
|
|
2
|
+
import { readdir, readFile } from "fs/promises";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
function getClaudeProjectsDir() {
|
|
6
|
+
return join(homedir(), ".claude", "projects");
|
|
7
|
+
}
|
|
8
|
+
function encodePath(absPath) {
|
|
9
|
+
return absPath.replace(/[/.]/g, "-");
|
|
10
|
+
}
|
|
11
|
+
async function listClaudeSessions(gitRoot) {
|
|
12
|
+
const projectsDir = getClaudeProjectsDir();
|
|
13
|
+
let projectDirs;
|
|
14
|
+
try {
|
|
15
|
+
projectDirs = await readdir(projectsDir);
|
|
16
|
+
} catch {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
const sessions = [];
|
|
20
|
+
for (const projectEntry of projectDirs) {
|
|
21
|
+
if (gitRoot) {
|
|
22
|
+
const encoded = encodePath(gitRoot);
|
|
23
|
+
if (projectEntry !== encoded)
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
const projectPath = join(projectsDir, projectEntry);
|
|
27
|
+
let files;
|
|
28
|
+
try {
|
|
29
|
+
files = await readdir(projectPath);
|
|
30
|
+
} catch {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
|
|
34
|
+
for (const jsonlFile of jsonlFiles) {
|
|
35
|
+
const sessionId = jsonlFile.replace(".jsonl", "");
|
|
36
|
+
const filePath = join(projectPath, jsonlFile);
|
|
37
|
+
let content;
|
|
38
|
+
try {
|
|
39
|
+
content = await readFile(filePath, "utf-8");
|
|
40
|
+
} catch {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
let cwd = null;
|
|
44
|
+
let branch = null;
|
|
45
|
+
let tsFirst = null;
|
|
46
|
+
let tsLast = null;
|
|
47
|
+
const userMessages = [];
|
|
48
|
+
for (const line of content.split("\n")) {
|
|
49
|
+
if (!line.trim())
|
|
50
|
+
continue;
|
|
51
|
+
let event;
|
|
52
|
+
try {
|
|
53
|
+
event = JSON.parse(line);
|
|
54
|
+
} catch {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (!cwd && typeof event.cwd === "string")
|
|
58
|
+
cwd = event.cwd;
|
|
59
|
+
if (!branch && typeof event.gitBranch === "string")
|
|
60
|
+
branch = event.gitBranch;
|
|
61
|
+
const ts = event.timestamp;
|
|
62
|
+
if (ts) {
|
|
63
|
+
if (!tsFirst)
|
|
64
|
+
tsFirst = ts;
|
|
65
|
+
tsLast = ts;
|
|
66
|
+
}
|
|
67
|
+
if (event.type === "user" && !event.isMeta) {
|
|
68
|
+
const msg = event.message;
|
|
69
|
+
const raw = msg?.content;
|
|
70
|
+
let text = null;
|
|
71
|
+
if (typeof raw === "string" && raw && !raw.startsWith("<")) {
|
|
72
|
+
text = raw.slice(0, 200).trim();
|
|
73
|
+
} else if (Array.isArray(raw)) {
|
|
74
|
+
for (const item of raw) {
|
|
75
|
+
if (item.type === "text" && typeof item.text === "string") {
|
|
76
|
+
const t = item.text.trim();
|
|
77
|
+
if (t && !t.startsWith("<")) {
|
|
78
|
+
text = t.slice(0, 200);
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (text && userMessages.length < 5)
|
|
85
|
+
userMessages.push(text);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (!tsFirst)
|
|
89
|
+
continue;
|
|
90
|
+
const summary = userMessages[0]?.slice(0, 100) || "";
|
|
91
|
+
sessions.push({
|
|
92
|
+
id: sessionId,
|
|
93
|
+
source: "claude",
|
|
94
|
+
summary,
|
|
95
|
+
branch,
|
|
96
|
+
repository: null,
|
|
97
|
+
git_root: cwd,
|
|
98
|
+
// treat cwd as git_root for filtering
|
|
99
|
+
cwd,
|
|
100
|
+
created_at: tsFirst,
|
|
101
|
+
updated_at: tsLast || tsFirst,
|
|
102
|
+
intents: [],
|
|
103
|
+
user_messages: userMessages
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
sessions.sort((a, b) => b.updated_at.localeCompare(a.updated_at));
|
|
108
|
+
return sessions;
|
|
109
|
+
}
|
|
110
|
+
async function computeClaudeSessionStats(sessionId) {
|
|
111
|
+
const projectsDir = getClaudeProjectsDir();
|
|
112
|
+
let projectDirs;
|
|
113
|
+
try {
|
|
114
|
+
projectDirs = await readdir(projectsDir);
|
|
115
|
+
} catch {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
for (const projectEntry of projectDirs) {
|
|
119
|
+
const filePath = join(projectsDir, projectEntry, `${sessionId}.jsonl`);
|
|
120
|
+
let content;
|
|
121
|
+
try {
|
|
122
|
+
content = await readFile(filePath, "utf-8");
|
|
123
|
+
} catch {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
let tsFirst = null;
|
|
127
|
+
let tsLast = null;
|
|
128
|
+
let turnCount = 0;
|
|
129
|
+
let toolCallCount = 0;
|
|
130
|
+
const modelBreakdown = {};
|
|
131
|
+
for (const line of content.split("\n")) {
|
|
132
|
+
if (!line.trim())
|
|
133
|
+
continue;
|
|
134
|
+
let event;
|
|
135
|
+
try {
|
|
136
|
+
event = JSON.parse(line);
|
|
137
|
+
} catch {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
const ts = event.timestamp;
|
|
141
|
+
if (ts) {
|
|
142
|
+
if (!tsFirst)
|
|
143
|
+
tsFirst = ts;
|
|
144
|
+
tsLast = ts;
|
|
145
|
+
}
|
|
146
|
+
if (event.type === "assistant") {
|
|
147
|
+
turnCount++;
|
|
148
|
+
const model = event.model;
|
|
149
|
+
if (model) {
|
|
150
|
+
modelBreakdown[model] = (modelBreakdown[model] ?? 0) + 1;
|
|
151
|
+
}
|
|
152
|
+
const msg = event.message;
|
|
153
|
+
const content_blocks = msg?.content;
|
|
154
|
+
if (Array.isArray(content_blocks)) {
|
|
155
|
+
for (const block of content_blocks) {
|
|
156
|
+
if (block.type === "tool_use")
|
|
157
|
+
toolCallCount++;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (!tsFirst)
|
|
163
|
+
continue;
|
|
164
|
+
const sessionDurationMs = tsLast && tsFirst ? new Date(tsLast).getTime() - new Date(tsFirst).getTime() : void 0;
|
|
165
|
+
return {
|
|
166
|
+
ended_at: tsLast ?? void 0,
|
|
167
|
+
session_duration_ms: sessionDurationMs,
|
|
168
|
+
turn_count: turnCount || void 0,
|
|
169
|
+
tool_call_count: toolCallCount || void 0,
|
|
170
|
+
model_breakdown: Object.keys(modelBreakdown).length > 0 ? modelBreakdown : void 0
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export {
|
|
177
|
+
getClaudeProjectsDir,
|
|
178
|
+
listClaudeSessions,
|
|
179
|
+
computeClaudeSessionStats
|
|
180
|
+
};
|
|
181
|
+
//# sourceMappingURL=chunk-CSA73CBK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../packages/core/dist/claude-sessions.js"],"sourcesContent":["import { readdir, readFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nexport function getClaudeProjectsDir() {\n return join(homedir(), \".claude\", \"projects\");\n}\n/** Encode a filesystem path the way Claude CLI does: replace slashes and dots with dashes. */\nfunction encodePath(absPath) {\n return absPath.replace(/[/.]/g, \"-\");\n}\nexport async function listClaudeSessions(gitRoot) {\n const projectsDir = getClaudeProjectsDir();\n let projectDirs;\n try {\n projectDirs = await readdir(projectsDir);\n }\n catch {\n return [];\n }\n const sessions = [];\n for (const projectEntry of projectDirs) {\n // Each entry is an encoded path like \"-Users-name-Projects-repo\"\n // Decode by replacing leading \"-\" and dashes between path components\n // We reconstruct the original path: leading \"-\" → \"/\" then subsequent \"-\" between\n // segments are also \"/\", but we can't distinguish dashes in folder names from separators.\n // Best heuristic: if gitRoot is provided, check if encoded gitRoot matches.\n if (gitRoot) {\n const encoded = encodePath(gitRoot);\n if (projectEntry !== encoded)\n continue;\n }\n const projectPath = join(projectsDir, projectEntry);\n let files;\n try {\n files = await readdir(projectPath);\n }\n catch {\n continue;\n }\n // Process each .jsonl session file\n const jsonlFiles = files.filter((f) => f.endsWith(\".jsonl\"));\n for (const jsonlFile of jsonlFiles) {\n const sessionId = jsonlFile.replace(\".jsonl\", \"\");\n const filePath = join(projectPath, jsonlFile);\n let content;\n try {\n content = await readFile(filePath, \"utf-8\");\n }\n catch {\n continue;\n }\n let cwd = null;\n let branch = null;\n let tsFirst = null;\n let tsLast = null;\n const userMessages = [];\n for (const line of content.split(\"\\n\")) {\n if (!line.trim())\n continue;\n let event;\n try {\n event = JSON.parse(line);\n }\n catch {\n continue;\n }\n if (!cwd && typeof event.cwd === \"string\")\n cwd = event.cwd;\n if (!branch && typeof event.gitBranch === \"string\")\n branch = event.gitBranch;\n const ts = event.timestamp;\n if (ts) {\n if (!tsFirst)\n tsFirst = ts;\n tsLast = ts;\n }\n if (event.type === \"user\" && !event.isMeta) {\n const msg = event.message;\n const raw = msg?.content;\n let text = null;\n if (typeof raw === \"string\" && raw && !raw.startsWith(\"<\")) {\n text = raw.slice(0, 200).trim();\n }\n else if (Array.isArray(raw)) {\n for (const item of raw) {\n if (item.type === \"text\" && typeof item.text === \"string\") {\n const t = item.text.trim();\n if (t && !t.startsWith(\"<\")) {\n text = t.slice(0, 200);\n break;\n }\n }\n }\n }\n if (text && userMessages.length < 5)\n userMessages.push(text);\n }\n }\n if (!tsFirst)\n continue; // skip empty/unreadable sessions\n // Use first user message as summary\n const summary = userMessages[0]?.slice(0, 100) || \"\";\n sessions.push({\n id: sessionId,\n source: \"claude\",\n summary,\n branch,\n repository: null,\n git_root: cwd, // treat cwd as git_root for filtering\n cwd,\n created_at: tsFirst,\n updated_at: tsLast || tsFirst,\n intents: [],\n user_messages: userMessages,\n });\n }\n }\n sessions.sort((a, b) => b.updated_at.localeCompare(a.updated_at));\n return sessions;\n}\n/**\n * Compute statistics for a Claude session from its .jsonl file.\n * Scans all project directories to find the session file.\n */\nexport async function computeClaudeSessionStats(sessionId) {\n const projectsDir = getClaudeProjectsDir();\n let projectDirs;\n try {\n projectDirs = await readdir(projectsDir);\n }\n catch {\n return null;\n }\n for (const projectEntry of projectDirs) {\n const filePath = join(projectsDir, projectEntry, `${sessionId}.jsonl`);\n let content;\n try {\n content = await readFile(filePath, \"utf-8\");\n }\n catch {\n continue;\n }\n let tsFirst = null;\n let tsLast = null;\n let turnCount = 0;\n let toolCallCount = 0;\n const modelBreakdown = {};\n for (const line of content.split(\"\\n\")) {\n if (!line.trim())\n continue;\n let event;\n try {\n event = JSON.parse(line);\n }\n catch {\n continue;\n }\n const ts = event.timestamp;\n if (ts) {\n if (!tsFirst)\n tsFirst = ts;\n tsLast = ts;\n }\n if (event.type === \"assistant\") {\n turnCount++;\n const model = event.model;\n if (model) {\n modelBreakdown[model] = (modelBreakdown[model] ?? 0) + 1;\n }\n // Count tool uses\n const msg = event.message;\n const content_blocks = msg?.content;\n if (Array.isArray(content_blocks)) {\n for (const block of content_blocks) {\n if (block.type === \"tool_use\")\n toolCallCount++;\n }\n }\n }\n }\n if (!tsFirst)\n continue;\n const sessionDurationMs = tsLast && tsFirst\n ? new Date(tsLast).getTime() - new Date(tsFirst).getTime()\n : undefined;\n return {\n ended_at: tsLast ?? undefined,\n session_duration_ms: sessionDurationMs,\n turn_count: turnCount || undefined,\n tool_call_count: toolCallCount || undefined,\n model_breakdown: Object.keys(modelBreakdown).length > 0 ? modelBreakdown : undefined,\n };\n }\n return null;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
// ../../packages/core/dist/gemini-sessions.js
|
|
2
|
+
import { readdir, readFile } from "fs/promises";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
function getGeminiTmpDir() {
|
|
6
|
+
return join(homedir(), ".gemini", "tmp");
|
|
7
|
+
}
|
|
8
|
+
async function getGeminiProjectName(_gitRoot) {
|
|
9
|
+
const projectsFile = join(homedir(), ".gemini", "projects.json");
|
|
10
|
+
const pathToName = /* @__PURE__ */ new Map();
|
|
11
|
+
try {
|
|
12
|
+
const raw = await readFile(projectsFile, "utf-8");
|
|
13
|
+
const data = JSON.parse(raw);
|
|
14
|
+
const projects = data.projects;
|
|
15
|
+
if (projects && typeof projects === "object") {
|
|
16
|
+
for (const [path, name] of Object.entries(projects)) {
|
|
17
|
+
pathToName.set(path, name);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
} catch {
|
|
21
|
+
}
|
|
22
|
+
return pathToName;
|
|
23
|
+
}
|
|
24
|
+
async function listGeminiSessions(gitRoot) {
|
|
25
|
+
const tmpDir = getGeminiTmpDir();
|
|
26
|
+
const pathToName = await getGeminiProjectName(gitRoot);
|
|
27
|
+
let projectNames;
|
|
28
|
+
if (gitRoot) {
|
|
29
|
+
const name = pathToName.get(gitRoot);
|
|
30
|
+
if (name) {
|
|
31
|
+
projectNames = [name];
|
|
32
|
+
} else {
|
|
33
|
+
try {
|
|
34
|
+
projectNames = await readdir(tmpDir);
|
|
35
|
+
} catch {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
} else {
|
|
40
|
+
try {
|
|
41
|
+
projectNames = await readdir(tmpDir);
|
|
42
|
+
} catch {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const nameToPath = /* @__PURE__ */ new Map();
|
|
47
|
+
for (const [path, name] of pathToName.entries()) {
|
|
48
|
+
nameToPath.set(name, path);
|
|
49
|
+
}
|
|
50
|
+
const sessions = [];
|
|
51
|
+
for (const projectName of projectNames) {
|
|
52
|
+
const chatsDir = join(tmpDir, projectName, "chats");
|
|
53
|
+
let chatFiles;
|
|
54
|
+
try {
|
|
55
|
+
chatFiles = await readdir(chatsDir);
|
|
56
|
+
} catch {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const projectPath = nameToPath.get(projectName) ?? null;
|
|
60
|
+
if (gitRoot && projectPath && projectPath !== gitRoot)
|
|
61
|
+
continue;
|
|
62
|
+
for (const chatFile of chatFiles.filter((f) => f.endsWith(".json"))) {
|
|
63
|
+
const filePath = join(chatsDir, chatFile);
|
|
64
|
+
let data;
|
|
65
|
+
try {
|
|
66
|
+
const raw = await readFile(filePath, "utf-8");
|
|
67
|
+
data = JSON.parse(raw);
|
|
68
|
+
} catch {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const sessionId = data.sessionId || chatFile.replace(".json", "");
|
|
72
|
+
const startTime = data.startTime || "";
|
|
73
|
+
const lastUpdated = data.lastUpdated || startTime;
|
|
74
|
+
if (!startTime)
|
|
75
|
+
continue;
|
|
76
|
+
const messages = data.messages || [];
|
|
77
|
+
const userMessages = [];
|
|
78
|
+
for (const msg of messages) {
|
|
79
|
+
if (msg.type !== "user")
|
|
80
|
+
continue;
|
|
81
|
+
const raw = msg.content;
|
|
82
|
+
let text = null;
|
|
83
|
+
if (typeof raw === "string" && raw.trim()) {
|
|
84
|
+
text = raw.trim().slice(0, 200);
|
|
85
|
+
} else if (Array.isArray(raw)) {
|
|
86
|
+
for (const item of raw) {
|
|
87
|
+
if (item.text && typeof item.text === "string") {
|
|
88
|
+
text = item.text.trim().slice(0, 200);
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (text && userMessages.length < 5)
|
|
94
|
+
userMessages.push(text);
|
|
95
|
+
}
|
|
96
|
+
const summary = typeof data.summary === "string" && data.summary.trim() ? data.summary.trim().slice(0, 100) : userMessages[0]?.slice(0, 100) || "";
|
|
97
|
+
sessions.push({
|
|
98
|
+
id: sessionId,
|
|
99
|
+
source: "gemini",
|
|
100
|
+
summary,
|
|
101
|
+
branch: null,
|
|
102
|
+
// Gemini CLI doesn't capture branch in session files
|
|
103
|
+
repository: null,
|
|
104
|
+
git_root: projectPath,
|
|
105
|
+
cwd: projectPath,
|
|
106
|
+
created_at: startTime,
|
|
107
|
+
updated_at: lastUpdated,
|
|
108
|
+
intents: [],
|
|
109
|
+
user_messages: userMessages
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
sessions.sort((a, b) => b.updated_at.localeCompare(a.updated_at));
|
|
114
|
+
return sessions;
|
|
115
|
+
}
|
|
116
|
+
async function computeGeminiSessionStats(sessionId) {
|
|
117
|
+
const tmpDir = getGeminiTmpDir();
|
|
118
|
+
let projectNames;
|
|
119
|
+
try {
|
|
120
|
+
projectNames = await readdir(tmpDir);
|
|
121
|
+
} catch {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
for (const projectName of projectNames) {
|
|
125
|
+
const filePath = join(tmpDir, projectName, "chats", `${sessionId}.json`);
|
|
126
|
+
let data;
|
|
127
|
+
try {
|
|
128
|
+
const raw = await readFile(filePath, "utf-8");
|
|
129
|
+
data = JSON.parse(raw);
|
|
130
|
+
} catch {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
const startTime = data.startTime;
|
|
134
|
+
const lastUpdated = data.lastUpdated || startTime;
|
|
135
|
+
if (!startTime)
|
|
136
|
+
continue;
|
|
137
|
+
const sessionDurationMs = lastUpdated && startTime ? new Date(lastUpdated).getTime() - new Date(startTime).getTime() : void 0;
|
|
138
|
+
const messages = data.messages || [];
|
|
139
|
+
let turnCount = 0;
|
|
140
|
+
let toolCallCount = 0;
|
|
141
|
+
const modelBreakdown = {};
|
|
142
|
+
for (const msg of messages) {
|
|
143
|
+
if (msg.type === "model" || msg.role === "model") {
|
|
144
|
+
turnCount++;
|
|
145
|
+
const model = msg.model;
|
|
146
|
+
if (model) {
|
|
147
|
+
modelBreakdown[model] = (modelBreakdown[model] ?? 0) + 1;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (msg.type === "tool" || msg.role === "tool") {
|
|
151
|
+
toolCallCount++;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
ended_at: lastUpdated ?? void 0,
|
|
156
|
+
session_duration_ms: sessionDurationMs,
|
|
157
|
+
turn_count: turnCount || void 0,
|
|
158
|
+
tool_call_count: toolCallCount || void 0,
|
|
159
|
+
model_breakdown: Object.keys(modelBreakdown).length > 0 ? modelBreakdown : void 0
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export {
|
|
166
|
+
getGeminiTmpDir,
|
|
167
|
+
listGeminiSessions,
|
|
168
|
+
computeGeminiSessionStats
|
|
169
|
+
};
|
|
170
|
+
//# sourceMappingURL=chunk-WYWBLQNM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../packages/core/dist/gemini-sessions.js"],"sourcesContent":["import { readdir, readFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nexport function getGeminiTmpDir() {\n return join(homedir(), \".gemini\", \"tmp\");\n}\nasync function getGeminiProjectName(_gitRoot) {\n // ~/.gemini/projects.json maps { \"projects\": { \"/abs/path\": \"project-name\" } }\n const projectsFile = join(homedir(), \".gemini\", \"projects.json\");\n const pathToName = new Map();\n try {\n const raw = await readFile(projectsFile, \"utf-8\");\n const data = JSON.parse(raw);\n const projects = data.projects;\n if (projects && typeof projects === \"object\") {\n for (const [path, name] of Object.entries(projects)) {\n pathToName.set(path, name);\n }\n }\n }\n catch {\n // projects.json missing or unreadable\n }\n return pathToName;\n}\nexport async function listGeminiSessions(gitRoot) {\n const tmpDir = getGeminiTmpDir();\n const pathToName = await getGeminiProjectName(gitRoot);\n // Determine which project directory names to scan\n let projectNames;\n if (gitRoot) {\n const name = pathToName.get(gitRoot);\n if (name) {\n projectNames = [name];\n }\n else {\n // Fall back to scanning all projects but filtering by path match\n try {\n projectNames = await readdir(tmpDir);\n }\n catch {\n return [];\n }\n }\n }\n else {\n try {\n projectNames = await readdir(tmpDir);\n }\n catch {\n return [];\n }\n }\n // Reverse-map name → original path for git_root\n const nameToPath = new Map();\n for (const [path, name] of pathToName.entries()) {\n nameToPath.set(name, path);\n }\n const sessions = [];\n for (const projectName of projectNames) {\n const chatsDir = join(tmpDir, projectName, \"chats\");\n let chatFiles;\n try {\n chatFiles = await readdir(chatsDir);\n }\n catch {\n continue;\n }\n const projectPath = nameToPath.get(projectName) ?? null;\n // If filtering by gitRoot and we know the path, skip non-matching\n if (gitRoot && projectPath && projectPath !== gitRoot)\n continue;\n for (const chatFile of chatFiles.filter((f) => f.endsWith(\".json\"))) {\n const filePath = join(chatsDir, chatFile);\n let data;\n try {\n const raw = await readFile(filePath, \"utf-8\");\n data = JSON.parse(raw);\n }\n catch {\n continue;\n }\n const sessionId = data.sessionId || chatFile.replace(\".json\", \"\");\n const startTime = data.startTime || \"\";\n const lastUpdated = data.lastUpdated || startTime;\n if (!startTime)\n continue;\n const messages = data.messages || [];\n const userMessages = [];\n for (const msg of messages) {\n if (msg.type !== \"user\")\n continue;\n const raw = msg.content;\n let text = null;\n if (typeof raw === \"string\" && raw.trim()) {\n text = raw.trim().slice(0, 200);\n }\n else if (Array.isArray(raw)) {\n for (const item of raw) {\n if (item.text && typeof item.text === \"string\") {\n text = item.text.trim().slice(0, 200);\n break;\n }\n }\n }\n if (text && userMessages.length < 5)\n userMessages.push(text);\n }\n // Use the summary field if present, else first user message\n const summary = typeof data.summary === \"string\" && data.summary.trim()\n ? data.summary.trim().slice(0, 100)\n : userMessages[0]?.slice(0, 100) || \"\";\n sessions.push({\n id: sessionId,\n source: \"gemini\",\n summary,\n branch: null, // Gemini CLI doesn't capture branch in session files\n repository: null,\n git_root: projectPath,\n cwd: projectPath,\n created_at: startTime,\n updated_at: lastUpdated,\n intents: [],\n user_messages: userMessages,\n });\n }\n }\n sessions.sort((a, b) => b.updated_at.localeCompare(a.updated_at));\n return sessions;\n}\n/**\n * Compute statistics for a Gemini session from its chat JSON file.\n * Scans all project directories to find the session file.\n */\nexport async function computeGeminiSessionStats(sessionId) {\n const tmpDir = getGeminiTmpDir();\n let projectNames;\n try {\n projectNames = await readdir(tmpDir);\n }\n catch {\n return null;\n }\n for (const projectName of projectNames) {\n const filePath = join(tmpDir, projectName, \"chats\", `${sessionId}.json`);\n let data;\n try {\n const raw = await readFile(filePath, \"utf-8\");\n data = JSON.parse(raw);\n }\n catch {\n continue;\n }\n const startTime = data.startTime;\n const lastUpdated = data.lastUpdated || startTime;\n if (!startTime)\n continue;\n const sessionDurationMs = lastUpdated && startTime\n ? new Date(lastUpdated).getTime() - new Date(startTime).getTime()\n : undefined;\n // Count turns and tool calls from messages\n const messages = data.messages || [];\n let turnCount = 0;\n let toolCallCount = 0;\n const modelBreakdown = {};\n for (const msg of messages) {\n if (msg.type === \"model\" || msg.role === \"model\") {\n turnCount++;\n const model = msg.model;\n if (model) {\n modelBreakdown[model] = (modelBreakdown[model] ?? 0) + 1;\n }\n }\n if (msg.type === \"tool\" || msg.role === \"tool\") {\n toolCallCount++;\n }\n }\n return {\n ended_at: lastUpdated ?? undefined,\n session_duration_ms: sessionDurationMs,\n turn_count: turnCount || undefined,\n tool_call_count: toolCallCount || undefined,\n model_breakdown: Object.keys(modelBreakdown).length > 0 ? modelBreakdown : undefined,\n };\n }\n return null;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
computeClaudeSessionStats,
|
|
3
|
+
getClaudeProjectsDir,
|
|
4
|
+
listClaudeSessions
|
|
5
|
+
} from "./chunk-CSA73CBK.js";
|
|
6
|
+
export {
|
|
7
|
+
computeClaudeSessionStats,
|
|
8
|
+
getClaudeProjectsDir,
|
|
9
|
+
listClaudeSessions
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=claude-sessions-5HEECZ63.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|