claude-sessions-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +145 -0
- package/dist/chunk-D2SPLCNE.js +48 -0
- package/dist/chunk-D2SPLCNE.js.map +1 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.js +369 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/server.d.ts +10 -0
- package/dist/server.js +9 -0
- package/dist/server.js.map +1 -0
- package/dist/web/client/_app/immutable/assets/0.BNKw18IQ.css +1 -0
- package/dist/web/client/_app/immutable/assets/0.BNKw18IQ.css.br +0 -0
- package/dist/web/client/_app/immutable/assets/0.BNKw18IQ.css.gz +0 -0
- package/dist/web/client/_app/immutable/chunks/9luERNGW.js +1 -0
- package/dist/web/client/_app/immutable/chunks/9luERNGW.js.br +0 -0
- package/dist/web/client/_app/immutable/chunks/9luERNGW.js.gz +0 -0
- package/dist/web/client/_app/immutable/chunks/B4TJWzDE.js +1 -0
- package/dist/web/client/_app/immutable/chunks/B4TJWzDE.js.br +0 -0
- package/dist/web/client/_app/immutable/chunks/B4TJWzDE.js.gz +0 -0
- package/dist/web/client/_app/immutable/chunks/ChOgyrGO.js +2 -0
- package/dist/web/client/_app/immutable/chunks/ChOgyrGO.js.br +0 -0
- package/dist/web/client/_app/immutable/chunks/ChOgyrGO.js.gz +0 -0
- package/dist/web/client/_app/immutable/chunks/DUFLAPco.js +1 -0
- package/dist/web/client/_app/immutable/chunks/DUFLAPco.js.br +0 -0
- package/dist/web/client/_app/immutable/chunks/DUFLAPco.js.gz +0 -0
- package/dist/web/client/_app/immutable/chunks/DbCO4Be8.js +1 -0
- package/dist/web/client/_app/immutable/chunks/DbCO4Be8.js.br +1 -0
- package/dist/web/client/_app/immutable/chunks/DbCO4Be8.js.gz +0 -0
- package/dist/web/client/_app/immutable/entry/app.CQAI9q_h.js +2 -0
- package/dist/web/client/_app/immutable/entry/app.CQAI9q_h.js.br +0 -0
- package/dist/web/client/_app/immutable/entry/app.CQAI9q_h.js.gz +0 -0
- package/dist/web/client/_app/immutable/entry/start.lvsJ5TiJ.js +1 -0
- package/dist/web/client/_app/immutable/entry/start.lvsJ5TiJ.js.br +2 -0
- package/dist/web/client/_app/immutable/entry/start.lvsJ5TiJ.js.gz +0 -0
- package/dist/web/client/_app/immutable/nodes/0.Cd8eOV9t.js +1 -0
- package/dist/web/client/_app/immutable/nodes/0.Cd8eOV9t.js.br +0 -0
- package/dist/web/client/_app/immutable/nodes/0.Cd8eOV9t.js.gz +0 -0
- package/dist/web/client/_app/immutable/nodes/1.DVVhj1K8.js +1 -0
- package/dist/web/client/_app/immutable/nodes/1.DVVhj1K8.js.br +0 -0
- package/dist/web/client/_app/immutable/nodes/1.DVVhj1K8.js.gz +0 -0
- package/dist/web/client/_app/immutable/nodes/2.DqJoUS41.js +1 -0
- package/dist/web/client/_app/immutable/nodes/2.DqJoUS41.js.br +0 -0
- package/dist/web/client/_app/immutable/nodes/2.DqJoUS41.js.gz +0 -0
- package/dist/web/client/_app/version.json +1 -0
- package/dist/web/client/_app/version.json.br +0 -0
- package/dist/web/client/_app/version.json.gz +0 -0
- package/dist/web/client/favicon.png +0 -0
- package/dist/web/env.js +45 -0
- package/dist/web/handler.js +1390 -0
- package/dist/web/index.js +334 -0
- package/dist/web/server/chunks/0-D_Gx_HFy.js +17 -0
- package/dist/web/server/chunks/0-D_Gx_HFy.js.map +1 -0
- package/dist/web/server/chunks/1-B6558drr.js +9 -0
- package/dist/web/server/chunks/1-B6558drr.js.map +1 -0
- package/dist/web/server/chunks/2-Bx262N6m.js +9 -0
- package/dist/web/server/chunks/2-Bx262N6m.js.map +1 -0
- package/dist/web/server/chunks/_layout.svelte-Dqh3cdpb.js +28 -0
- package/dist/web/server/chunks/_layout.svelte-Dqh3cdpb.js.map +1 -0
- package/dist/web/server/chunks/_page.svelte-BzYm1sb_.js +65 -0
- package/dist/web/server/chunks/_page.svelte-BzYm1sb_.js.map +1 -0
- package/dist/web/server/chunks/_server.ts-BXCTPhzX.js +28 -0
- package/dist/web/server/chunks/_server.ts-BXCTPhzX.js.map +1 -0
- package/dist/web/server/chunks/_server.ts-Bxqo8tYS.js +14 -0
- package/dist/web/server/chunks/_server.ts-Bxqo8tYS.js.map +1 -0
- package/dist/web/server/chunks/_server.ts-C5Bf6au4.js +26 -0
- package/dist/web/server/chunks/_server.ts-C5Bf6au4.js.map +1 -0
- package/dist/web/server/chunks/_server.ts-C88EFL4g.js +19 -0
- package/dist/web/server/chunks/_server.ts-C88EFL4g.js.map +1 -0
- package/dist/web/server/chunks/_server.ts-D8vMV9FN.js +18 -0
- package/dist/web/server/chunks/_server.ts-D8vMV9FN.js.map +1 -0
- package/dist/web/server/chunks/_server.ts-DW3OhHzP.js +37 -0
- package/dist/web/server/chunks/_server.ts-DW3OhHzP.js.map +1 -0
- package/dist/web/server/chunks/_server.ts-Ne8LE0dr.js +18 -0
- package/dist/web/server/chunks/_server.ts-Ne8LE0dr.js.map +1 -0
- package/dist/web/server/chunks/context-R2425nfV.js +64 -0
- package/dist/web/server/chunks/context-R2425nfV.js.map +1 -0
- package/dist/web/server/chunks/error.svelte-DazOwnSn.js +45 -0
- package/dist/web/server/chunks/error.svelte-DazOwnSn.js.map +1 -0
- package/dist/web/server/chunks/exports-BzHwARwz.js +326 -0
- package/dist/web/server/chunks/exports-BzHwARwz.js.map +1 -0
- package/dist/web/server/chunks/index-CXFDTUAl.js +913 -0
- package/dist/web/server/chunks/index-CXFDTUAl.js.map +1 -0
- package/dist/web/server/chunks/index-CoD1IJuy.js +190 -0
- package/dist/web/server/chunks/index-CoD1IJuy.js.map +1 -0
- package/dist/web/server/chunks/session-ioLIuaZI.js +302 -0
- package/dist/web/server/chunks/session-ioLIuaZI.js.map +1 -0
- package/dist/web/server/index.js +8132 -0
- package/dist/web/server/index.js.map +1 -0
- package/dist/web/server/manifest.js +95 -0
- package/dist/web/server/manifest.js.map +1 -0
- package/dist/web/shims.js +32 -0
- package/package.json +94 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 es6.kr <drumrobot43@gmail.com>
|
|
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,145 @@
|
|
|
1
|
+
# claude-sessions-mcp
|
|
2
|
+
|
|
3
|
+
MCP (Model Context Protocol) server and Web UI for managing Claude Code sessions.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Project Listing**: Browse Claude Code project folders
|
|
8
|
+
- **Session Management**: List, rename, and delete sessions
|
|
9
|
+
- **Message Management**: View and delete messages within sessions
|
|
10
|
+
- **Cleanup**: Clear empty sessions and remove invalid API key messages
|
|
11
|
+
- **Web UI**: SvelteKit-based web interface
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Using npx (recommended)
|
|
17
|
+
npx @es6kr/claude-sessions-mcp
|
|
18
|
+
|
|
19
|
+
# Or install globally
|
|
20
|
+
npm install -g @es6kr/claude-sessions-mcp
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
### Claude Code MCP Integration
|
|
26
|
+
|
|
27
|
+
Add to Claude Code:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
claude mcp add claude-sessions -- npx @es6kr/claude-sessions-mcp
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Or manually edit `~/.claude.json`:
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"mcpServers": {
|
|
38
|
+
"claude-sessions": {
|
|
39
|
+
"command": "npx",
|
|
40
|
+
"args": ["@es6kr/claude-sessions-mcp"]
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Web GUI
|
|
47
|
+
|
|
48
|
+
Launch the web interface via MCP tool (from Claude Code):
|
|
49
|
+
|
|
50
|
+
```text
|
|
51
|
+
> Use the start_gui tool to launch web interface
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The GUI opens at `http://localhost:5050` with features:
|
|
55
|
+
|
|
56
|
+
- Browse all projects and sessions
|
|
57
|
+
- View full conversation history
|
|
58
|
+
- Rename sessions with inline editing
|
|
59
|
+
- Delete unwanted sessions
|
|
60
|
+
- Bulk cleanup of empty sessions
|
|
61
|
+
|
|
62
|
+
## Development
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# Enable corepack
|
|
66
|
+
corepack enable
|
|
67
|
+
|
|
68
|
+
# Install dependencies
|
|
69
|
+
pnpm install
|
|
70
|
+
|
|
71
|
+
# Start web development server
|
|
72
|
+
pnpm dev
|
|
73
|
+
|
|
74
|
+
# MCP server development mode
|
|
75
|
+
pnpm dev:mcp
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Build
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
pnpm build
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## MCP Server Tools
|
|
85
|
+
|
|
86
|
+
### Available Tools
|
|
87
|
+
|
|
88
|
+
| Tool | Description |
|
|
89
|
+
| ----------------- | ----------------------------------------- |
|
|
90
|
+
| `list_projects` | List Claude Code projects |
|
|
91
|
+
| `list_sessions` | List sessions in a project |
|
|
92
|
+
| `rename_session` | Rename a session |
|
|
93
|
+
| `delete_session` | Delete a session (moves to backup folder) |
|
|
94
|
+
| `delete_message` | Delete a message and repair UUID chain |
|
|
95
|
+
| `preview_cleanup` | Preview sessions to be cleaned |
|
|
96
|
+
| `clear_sessions` | Clear empty sessions and invalid messages |
|
|
97
|
+
| `start_gui` | Start the web UI |
|
|
98
|
+
| `stop_gui` | Stop the web UI |
|
|
99
|
+
|
|
100
|
+
## Tech Stack
|
|
101
|
+
|
|
102
|
+
- **MCP Server**: Node.js + TypeScript + Effect
|
|
103
|
+
- **Web UI**: SvelteKit + Svelte 5
|
|
104
|
+
- **Build**: tsup (MCP), Vite (Web)
|
|
105
|
+
- **Package Manager**: pnpm (corepack)
|
|
106
|
+
|
|
107
|
+
## Effect-TS Patterns
|
|
108
|
+
|
|
109
|
+
This project uses [Effect](https://effect.website) for functional async operations:
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
import { Effect, pipe, Array as A, Option as O } from 'effect'
|
|
113
|
+
|
|
114
|
+
// Define an Effect (lazy, composable)
|
|
115
|
+
const listProjects = Effect.gen(function* () {
|
|
116
|
+
const files = yield* Effect.tryPromise(() => fs.readdir(dir))
|
|
117
|
+
return files.filter((f) => f.endsWith('.jsonl'))
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
// Parallel execution with concurrency control
|
|
121
|
+
const results =
|
|
122
|
+
yield *
|
|
123
|
+
Effect.all(
|
|
124
|
+
items.map((item) => processItem(item)),
|
|
125
|
+
{ concurrency: 10 }
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
// Option for nullable values
|
|
129
|
+
const title = pipe(
|
|
130
|
+
messages,
|
|
131
|
+
A.findFirst((m) => m.type === 'user'),
|
|
132
|
+
O.map((m) => extractTitle(m)),
|
|
133
|
+
O.getOrElse(() => 'Untitled')
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
// Run in SvelteKit endpoint
|
|
137
|
+
export const GET = async () => {
|
|
138
|
+
const result = await Effect.runPromise(listProjects)
|
|
139
|
+
return json(result)
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## License
|
|
144
|
+
|
|
145
|
+
MIT
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// src/server.ts
|
|
2
|
+
import { spawn } from "child_process";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
import path from "path";
|
|
5
|
+
var __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
var webDir = path.join(__dirname, "..", "web");
|
|
7
|
+
async function startWebServer(port = 5050, openBrowser = true) {
|
|
8
|
+
const child = spawn("pnpm", ["preview", "--port", String(port)], {
|
|
9
|
+
cwd: webDir,
|
|
10
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
11
|
+
env: { ...process.env, PORT: String(port) }
|
|
12
|
+
});
|
|
13
|
+
await new Promise((resolve, reject) => {
|
|
14
|
+
const timeout = setTimeout(() => reject(new Error("Server startup timeout")), 1e4);
|
|
15
|
+
child.stdout?.on("data", (data) => {
|
|
16
|
+
const output = data.toString();
|
|
17
|
+
if (output.includes("localhost") || output.includes("Local:")) {
|
|
18
|
+
clearTimeout(timeout);
|
|
19
|
+
resolve();
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
child.on("error", (err) => {
|
|
23
|
+
clearTimeout(timeout);
|
|
24
|
+
reject(err);
|
|
25
|
+
});
|
|
26
|
+
child.on("exit", (code) => {
|
|
27
|
+
if (code !== 0) {
|
|
28
|
+
clearTimeout(timeout);
|
|
29
|
+
reject(new Error(`Server exited with code ${code}`));
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
if (openBrowser) {
|
|
34
|
+
const url = `http://localhost:${port}`;
|
|
35
|
+
const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
36
|
+
spawn(openCmd, [url], { stdio: "ignore", detached: true }).unref();
|
|
37
|
+
}
|
|
38
|
+
return { process: child, port };
|
|
39
|
+
}
|
|
40
|
+
async function stopWebServer(server) {
|
|
41
|
+
server.process.kill("SIGTERM");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export {
|
|
45
|
+
startWebServer,
|
|
46
|
+
stopWebServer
|
|
47
|
+
};
|
|
48
|
+
//# sourceMappingURL=chunk-D2SPLCNE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server.ts"],"sourcesContent":["import { spawn, type ChildProcess } from 'node:child_process'\nimport { fileURLToPath } from 'node:url'\nimport path from 'node:path'\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url))\nconst webDir = path.join(__dirname, '..', 'web')\n\ninterface WebServer {\n process: ChildProcess\n port: number\n}\n\nexport async function startWebServer(\n port: number = 5050,\n openBrowser: boolean = true\n): Promise<WebServer> {\n // Run SvelteKit preview\n const child = spawn('pnpm', ['preview', '--port', String(port)], {\n cwd: webDir,\n stdio: ['ignore', 'pipe', 'pipe'],\n env: { ...process.env, PORT: String(port) },\n })\n\n // Wait for server to start\n await new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(() => reject(new Error('Server startup timeout')), 10000)\n\n child.stdout?.on('data', (data: Buffer) => {\n const output = data.toString()\n if (output.includes('localhost') || output.includes('Local:')) {\n clearTimeout(timeout)\n resolve()\n }\n })\n\n child.on('error', (err) => {\n clearTimeout(timeout)\n reject(err)\n })\n\n child.on('exit', (code) => {\n if (code !== 0) {\n clearTimeout(timeout)\n reject(new Error(`Server exited with code ${code}`))\n }\n })\n })\n\n // Open browser if requested\n if (openBrowser) {\n const url = `http://localhost:${port}`\n const openCmd =\n process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open'\n spawn(openCmd, [url], { stdio: 'ignore', detached: true }).unref()\n }\n\n return { process: child, port }\n}\n\nexport async function stopWebServer(server: WebServer): Promise<void> {\n server.process.kill('SIGTERM')\n}\n"],"mappings":";AAAA,SAAS,aAAgC;AACzC,SAAS,qBAAqB;AAC9B,OAAO,UAAU;AAEjB,IAAM,YAAY,KAAK,QAAQ,cAAc,YAAY,GAAG,CAAC;AAC7D,IAAM,SAAS,KAAK,KAAK,WAAW,MAAM,KAAK;AAO/C,eAAsB,eACpB,OAAe,MACf,cAAuB,MACH;AAEpB,QAAM,QAAQ,MAAM,QAAQ,CAAC,WAAW,UAAU,OAAO,IAAI,CAAC,GAAG;AAAA,IAC/D,KAAK;AAAA,IACL,OAAO,CAAC,UAAU,QAAQ,MAAM;AAAA,IAChC,KAAK,EAAE,GAAG,QAAQ,KAAK,MAAM,OAAO,IAAI,EAAE;AAAA,EAC5C,CAAC;AAGD,QAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,UAAM,UAAU,WAAW,MAAM,OAAO,IAAI,MAAM,wBAAwB,CAAC,GAAG,GAAK;AAEnF,UAAM,QAAQ,GAAG,QAAQ,CAAC,SAAiB;AACzC,YAAM,SAAS,KAAK,SAAS;AAC7B,UAAI,OAAO,SAAS,WAAW,KAAK,OAAO,SAAS,QAAQ,GAAG;AAC7D,qBAAa,OAAO;AACpB,gBAAQ;AAAA,MACV;AAAA,IACF,CAAC;AAED,UAAM,GAAG,SAAS,CAAC,QAAQ;AACzB,mBAAa,OAAO;AACpB,aAAO,GAAG;AAAA,IACZ,CAAC;AAED,UAAM,GAAG,QAAQ,CAAC,SAAS;AACzB,UAAI,SAAS,GAAG;AACd,qBAAa,OAAO;AACpB,eAAO,IAAI,MAAM,2BAA2B,IAAI,EAAE,CAAC;AAAA,MACrD;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AAGD,MAAI,aAAa;AACf,UAAM,MAAM,oBAAoB,IAAI;AACpC,UAAM,UACJ,QAAQ,aAAa,WAAW,SAAS,QAAQ,aAAa,UAAU,UAAU;AACpF,UAAM,SAAS,CAAC,GAAG,GAAG,EAAE,OAAO,UAAU,UAAU,KAAK,CAAC,EAAE,MAAM;AAAA,EACnE;AAEA,SAAO,EAAE,SAAS,OAAO,KAAK;AAChC;AAEA,eAAsB,cAAc,QAAkC;AACpE,SAAO,QAAQ,KAAK,SAAS;AAC/B;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
startWebServer,
|
|
4
|
+
stopWebServer
|
|
5
|
+
} from "../chunk-D2SPLCNE.js";
|
|
6
|
+
|
|
7
|
+
// src/mcp/index.ts
|
|
8
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
10
|
+
import { Effect as Effect2 } from "effect";
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
|
|
13
|
+
// src/lib/session.ts
|
|
14
|
+
import { Effect, pipe, Array as A, Option as O } from "effect";
|
|
15
|
+
import * as fs from "fs/promises";
|
|
16
|
+
import * as path from "path";
|
|
17
|
+
import * as os from "os";
|
|
18
|
+
var getSessionsDir = () => path.join(os.homedir(), ".claude", "projects");
|
|
19
|
+
var listProjects = Effect.gen(function* () {
|
|
20
|
+
const sessionsDir = getSessionsDir();
|
|
21
|
+
const exists = yield* Effect.tryPromise(
|
|
22
|
+
() => fs.access(sessionsDir).then(() => true).catch(() => false)
|
|
23
|
+
);
|
|
24
|
+
if (!exists) {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
const entries = yield* Effect.tryPromise(() => fs.readdir(sessionsDir, { withFileTypes: true }));
|
|
28
|
+
const projects = yield* Effect.all(
|
|
29
|
+
entries.filter((e) => e.isDirectory()).map(
|
|
30
|
+
(entry) => Effect.gen(function* () {
|
|
31
|
+
const projectPath = path.join(sessionsDir, entry.name);
|
|
32
|
+
const files = yield* Effect.tryPromise(() => fs.readdir(projectPath));
|
|
33
|
+
const sessionFiles = files.filter((f) => f.endsWith(".jsonl"));
|
|
34
|
+
return {
|
|
35
|
+
name: entry.name,
|
|
36
|
+
path: projectPath,
|
|
37
|
+
sessionCount: sessionFiles.length
|
|
38
|
+
};
|
|
39
|
+
})
|
|
40
|
+
),
|
|
41
|
+
{ concurrency: 10 }
|
|
42
|
+
);
|
|
43
|
+
return projects;
|
|
44
|
+
});
|
|
45
|
+
var listSessions = (projectName) => Effect.gen(function* () {
|
|
46
|
+
const projectPath = path.join(getSessionsDir(), projectName);
|
|
47
|
+
const files = yield* Effect.tryPromise(() => fs.readdir(projectPath));
|
|
48
|
+
const sessionFiles = files.filter((f) => f.endsWith(".jsonl"));
|
|
49
|
+
const sessions = yield* Effect.all(
|
|
50
|
+
sessionFiles.map(
|
|
51
|
+
(file) => Effect.gen(function* () {
|
|
52
|
+
const filePath = path.join(projectPath, file);
|
|
53
|
+
const content = yield* Effect.tryPromise(() => fs.readFile(filePath, "utf-8"));
|
|
54
|
+
const lines = content.trim().split("\n").filter(Boolean);
|
|
55
|
+
const messages = lines.map((line) => JSON.parse(line));
|
|
56
|
+
const sessionId = file.replace(".jsonl", "");
|
|
57
|
+
const firstMessage = messages[0];
|
|
58
|
+
const lastMessage = messages[messages.length - 1];
|
|
59
|
+
const title = pipe(
|
|
60
|
+
messages,
|
|
61
|
+
A.findFirst((m) => m.type === "human"),
|
|
62
|
+
O.map((m) => {
|
|
63
|
+
const msg = m.message;
|
|
64
|
+
const content2 = msg?.content ?? "";
|
|
65
|
+
return content2.slice(0, 50) + (content2.length > 50 ? "..." : "");
|
|
66
|
+
}),
|
|
67
|
+
O.getOrElse(() => "Untitled")
|
|
68
|
+
);
|
|
69
|
+
return {
|
|
70
|
+
id: sessionId,
|
|
71
|
+
projectName,
|
|
72
|
+
title,
|
|
73
|
+
messageCount: messages.length,
|
|
74
|
+
createdAt: firstMessage?.timestamp,
|
|
75
|
+
updatedAt: lastMessage?.timestamp
|
|
76
|
+
};
|
|
77
|
+
})
|
|
78
|
+
),
|
|
79
|
+
{ concurrency: 10 }
|
|
80
|
+
);
|
|
81
|
+
return sessions;
|
|
82
|
+
});
|
|
83
|
+
var deleteSession = (projectName, sessionId) => Effect.gen(function* () {
|
|
84
|
+
const sessionsDir = getSessionsDir();
|
|
85
|
+
const filePath = path.join(sessionsDir, projectName, `${sessionId}.jsonl`);
|
|
86
|
+
const backupDir = path.join(sessionsDir, projectName, ".bak");
|
|
87
|
+
yield* Effect.tryPromise(() => fs.mkdir(backupDir, { recursive: true }));
|
|
88
|
+
const backupPath = path.join(backupDir, `${sessionId}.jsonl`);
|
|
89
|
+
yield* Effect.tryPromise(() => fs.rename(filePath, backupPath));
|
|
90
|
+
return { success: true, backupPath };
|
|
91
|
+
});
|
|
92
|
+
var renameSession = (projectName, sessionId, newTitle) => Effect.gen(function* () {
|
|
93
|
+
const filePath = path.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
|
|
94
|
+
const content = yield* Effect.tryPromise(() => fs.readFile(filePath, "utf-8"));
|
|
95
|
+
const lines = content.trim().split("\n").filter(Boolean);
|
|
96
|
+
if (lines.length === 0) {
|
|
97
|
+
return { success: false, error: "Empty session" };
|
|
98
|
+
}
|
|
99
|
+
const messages = lines.map((line) => JSON.parse(line));
|
|
100
|
+
const firstMsg = messages[0];
|
|
101
|
+
if (firstMsg && typeof firstMsg.message === "object" && firstMsg.message !== null) {
|
|
102
|
+
const msg = firstMsg.message;
|
|
103
|
+
if (msg.content) {
|
|
104
|
+
const cleanContent = msg.content.replace(/^\[.*?\]\s*/, "");
|
|
105
|
+
msg.content = `[${newTitle}] ${cleanContent}`;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const newContent = messages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
109
|
+
yield* Effect.tryPromise(() => fs.writeFile(filePath, newContent, "utf-8"));
|
|
110
|
+
return { success: true };
|
|
111
|
+
});
|
|
112
|
+
var deleteMessage = (projectName, sessionId, messageUuid) => Effect.gen(function* () {
|
|
113
|
+
const filePath = path.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
|
|
114
|
+
const content = yield* Effect.tryPromise(() => fs.readFile(filePath, "utf-8"));
|
|
115
|
+
const lines = content.trim().split("\n").filter(Boolean);
|
|
116
|
+
const messages = lines.map((line) => JSON.parse(line));
|
|
117
|
+
const targetIndex = messages.findIndex((m) => m.uuid === messageUuid);
|
|
118
|
+
if (targetIndex === -1) {
|
|
119
|
+
return { success: false, error: "Message not found" };
|
|
120
|
+
}
|
|
121
|
+
const deletedMsg = messages[targetIndex];
|
|
122
|
+
const parentUuid = deletedMsg?.parentUuid;
|
|
123
|
+
const nextMsg = messages[targetIndex + 1];
|
|
124
|
+
if (nextMsg) {
|
|
125
|
+
nextMsg.parentUuid = parentUuid;
|
|
126
|
+
}
|
|
127
|
+
messages.splice(targetIndex, 1);
|
|
128
|
+
const newContent = messages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
129
|
+
yield* Effect.tryPromise(() => fs.writeFile(filePath, newContent, "utf-8"));
|
|
130
|
+
return { success: true };
|
|
131
|
+
});
|
|
132
|
+
var previewCleanup = (projectName) => Effect.gen(function* () {
|
|
133
|
+
const projects = yield* listProjects;
|
|
134
|
+
const targetProjects = projectName ? projects.filter((p) => p.name === projectName) : projects;
|
|
135
|
+
const results = yield* Effect.all(
|
|
136
|
+
targetProjects.map(
|
|
137
|
+
(project) => Effect.gen(function* () {
|
|
138
|
+
const sessions = yield* listSessions(project.name);
|
|
139
|
+
const emptySessions = sessions.filter((s) => s.messageCount === 0);
|
|
140
|
+
const invalidSessions = sessions.filter(
|
|
141
|
+
(s) => s.title?.includes("Invalid API key") || s.title?.includes("API key")
|
|
142
|
+
);
|
|
143
|
+
return {
|
|
144
|
+
project: project.name,
|
|
145
|
+
emptySessions,
|
|
146
|
+
invalidSessions
|
|
147
|
+
};
|
|
148
|
+
})
|
|
149
|
+
),
|
|
150
|
+
{ concurrency: 5 }
|
|
151
|
+
);
|
|
152
|
+
return results;
|
|
153
|
+
});
|
|
154
|
+
var clearSessions = (options) => Effect.gen(function* () {
|
|
155
|
+
const { projectName, clearEmpty = true, clearInvalid = true } = options;
|
|
156
|
+
const cleanupPreview = yield* previewCleanup(projectName);
|
|
157
|
+
let deletedCount = 0;
|
|
158
|
+
for (const result of cleanupPreview) {
|
|
159
|
+
const toDelete = [
|
|
160
|
+
...clearEmpty ? result.emptySessions : [],
|
|
161
|
+
...clearInvalid ? result.invalidSessions : []
|
|
162
|
+
];
|
|
163
|
+
for (const session of toDelete) {
|
|
164
|
+
yield* deleteSession(result.project, session.id);
|
|
165
|
+
deletedCount++;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return { success: true, deletedCount };
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// src/mcp/index.ts
|
|
172
|
+
var server = new McpServer({
|
|
173
|
+
name: "claude-sessions-mcp",
|
|
174
|
+
version: "0.1.0"
|
|
175
|
+
});
|
|
176
|
+
server.tool("list_projects", "List all Claude Code projects with session counts", {}, async () => {
|
|
177
|
+
const result = await Effect2.runPromise(listProjects);
|
|
178
|
+
return {
|
|
179
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
180
|
+
};
|
|
181
|
+
});
|
|
182
|
+
server.tool(
|
|
183
|
+
"list_sessions",
|
|
184
|
+
"List all sessions in a project",
|
|
185
|
+
{
|
|
186
|
+
project_name: z.string().describe("Project folder name (e.g., '-Users-young-works-myproject')")
|
|
187
|
+
},
|
|
188
|
+
async ({ project_name }) => {
|
|
189
|
+
const result = await Effect2.runPromise(listSessions(project_name));
|
|
190
|
+
return {
|
|
191
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
);
|
|
195
|
+
server.tool(
|
|
196
|
+
"rename_session",
|
|
197
|
+
"Rename a session by adding a title prefix to the first message",
|
|
198
|
+
{
|
|
199
|
+
project_name: z.string().describe("Project folder name"),
|
|
200
|
+
session_id: z.string().describe("Session ID (filename without .jsonl)"),
|
|
201
|
+
new_title: z.string().describe("New title to add as prefix")
|
|
202
|
+
},
|
|
203
|
+
async ({ project_name, session_id, new_title }) => {
|
|
204
|
+
const result = await Effect2.runPromise(
|
|
205
|
+
renameSession(project_name, session_id, new_title)
|
|
206
|
+
);
|
|
207
|
+
return {
|
|
208
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
);
|
|
212
|
+
server.tool(
|
|
213
|
+
"delete_session",
|
|
214
|
+
"Delete a session (moves to .bak folder for recovery)",
|
|
215
|
+
{
|
|
216
|
+
project_name: z.string().describe("Project folder name"),
|
|
217
|
+
session_id: z.string().describe("Session ID to delete")
|
|
218
|
+
},
|
|
219
|
+
async ({ project_name, session_id }) => {
|
|
220
|
+
const result = await Effect2.runPromise(deleteSession(project_name, session_id));
|
|
221
|
+
return {
|
|
222
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
);
|
|
226
|
+
server.tool(
|
|
227
|
+
"delete_message",
|
|
228
|
+
"Delete a message from a session and repair the parentUuid chain",
|
|
229
|
+
{
|
|
230
|
+
project_name: z.string().describe("Project folder name"),
|
|
231
|
+
session_id: z.string().describe("Session ID"),
|
|
232
|
+
message_uuid: z.string().describe("UUID of the message to delete")
|
|
233
|
+
},
|
|
234
|
+
async ({ project_name, session_id, message_uuid }) => {
|
|
235
|
+
const result = await Effect2.runPromise(
|
|
236
|
+
deleteMessage(project_name, session_id, message_uuid)
|
|
237
|
+
);
|
|
238
|
+
return {
|
|
239
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
);
|
|
243
|
+
server.tool(
|
|
244
|
+
"preview_cleanup",
|
|
245
|
+
"Preview sessions that would be cleaned (empty and invalid API key sessions)",
|
|
246
|
+
{
|
|
247
|
+
project_name: z.string().optional().describe("Optional: filter by project name")
|
|
248
|
+
},
|
|
249
|
+
async ({ project_name }) => {
|
|
250
|
+
const result = await Effect2.runPromise(previewCleanup(project_name));
|
|
251
|
+
return {
|
|
252
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
);
|
|
256
|
+
server.tool(
|
|
257
|
+
"clear_sessions",
|
|
258
|
+
"Delete all empty sessions and invalid API key sessions",
|
|
259
|
+
{
|
|
260
|
+
project_name: z.string().optional().describe("Optional: filter by project name"),
|
|
261
|
+
clear_empty: z.boolean().default(true).describe("Clear empty sessions (default: true)"),
|
|
262
|
+
clear_invalid: z.boolean().default(true).describe("Clear invalid API key sessions (default: true)")
|
|
263
|
+
},
|
|
264
|
+
async ({ project_name, clear_empty, clear_invalid }) => {
|
|
265
|
+
const result = await Effect2.runPromise(
|
|
266
|
+
clearSessions({
|
|
267
|
+
projectName: project_name,
|
|
268
|
+
clearEmpty: clear_empty,
|
|
269
|
+
clearInvalid: clear_invalid
|
|
270
|
+
})
|
|
271
|
+
);
|
|
272
|
+
return {
|
|
273
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
);
|
|
277
|
+
var webServerInstance = null;
|
|
278
|
+
server.tool(
|
|
279
|
+
"start_gui",
|
|
280
|
+
"Start the web GUI for session management and open it in browser",
|
|
281
|
+
{
|
|
282
|
+
port: z.number().default(5050).describe("Port to run the web server on (default: 5050)"),
|
|
283
|
+
open_browser: z.boolean().default(true).describe("Whether to open browser automatically (default: true)")
|
|
284
|
+
},
|
|
285
|
+
async ({ port, open_browser }) => {
|
|
286
|
+
try {
|
|
287
|
+
if (webServerInstance) {
|
|
288
|
+
return {
|
|
289
|
+
content: [
|
|
290
|
+
{
|
|
291
|
+
type: "text",
|
|
292
|
+
text: JSON.stringify(
|
|
293
|
+
{
|
|
294
|
+
success: true,
|
|
295
|
+
message: "Web GUI is already running",
|
|
296
|
+
url: `http://localhost:${port}`
|
|
297
|
+
},
|
|
298
|
+
null,
|
|
299
|
+
2
|
|
300
|
+
)
|
|
301
|
+
}
|
|
302
|
+
]
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
webServerInstance = await startWebServer(port, open_browser);
|
|
306
|
+
return {
|
|
307
|
+
content: [
|
|
308
|
+
{
|
|
309
|
+
type: "text",
|
|
310
|
+
text: JSON.stringify(
|
|
311
|
+
{
|
|
312
|
+
success: true,
|
|
313
|
+
message: "Web GUI started successfully",
|
|
314
|
+
url: `http://localhost:${port}`,
|
|
315
|
+
pid: process.pid
|
|
316
|
+
},
|
|
317
|
+
null,
|
|
318
|
+
2
|
|
319
|
+
)
|
|
320
|
+
}
|
|
321
|
+
]
|
|
322
|
+
};
|
|
323
|
+
} catch (error) {
|
|
324
|
+
return {
|
|
325
|
+
content: [
|
|
326
|
+
{
|
|
327
|
+
type: "text",
|
|
328
|
+
text: JSON.stringify(
|
|
329
|
+
{
|
|
330
|
+
success: false,
|
|
331
|
+
error: String(error)
|
|
332
|
+
},
|
|
333
|
+
null,
|
|
334
|
+
2
|
|
335
|
+
)
|
|
336
|
+
}
|
|
337
|
+
]
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
);
|
|
342
|
+
server.tool("stop_gui", "Stop the web GUI server", {}, async () => {
|
|
343
|
+
if (webServerInstance) {
|
|
344
|
+
await stopWebServer(webServerInstance);
|
|
345
|
+
webServerInstance = null;
|
|
346
|
+
return {
|
|
347
|
+
content: [
|
|
348
|
+
{
|
|
349
|
+
type: "text",
|
|
350
|
+
text: JSON.stringify({ success: true, message: "Web GUI stopped successfully" }, null, 2)
|
|
351
|
+
}
|
|
352
|
+
]
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
return {
|
|
356
|
+
content: [
|
|
357
|
+
{
|
|
358
|
+
type: "text",
|
|
359
|
+
text: JSON.stringify({ success: true, message: "Web GUI was not running" }, null, 2)
|
|
360
|
+
}
|
|
361
|
+
]
|
|
362
|
+
};
|
|
363
|
+
});
|
|
364
|
+
async function main() {
|
|
365
|
+
const transport = new StdioServerTransport();
|
|
366
|
+
await server.connect(transport);
|
|
367
|
+
}
|
|
368
|
+
main().catch(console.error);
|
|
369
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/mcp/index.ts","../../src/lib/session.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport { Effect } from 'effect'\nimport { z } from 'zod'\nimport * as session from '../lib/session.js'\nimport { startWebServer, stopWebServer } from '../server.js'\n\nconst server = new McpServer({\n name: 'claude-sessions-mcp',\n version: '0.1.0',\n})\n\n// List all projects\nserver.tool('list_projects', 'List all Claude Code projects with session counts', {}, async () => {\n const result = await Effect.runPromise(session.listProjects)\n return {\n content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],\n }\n})\n\n// List sessions in a project\nserver.tool(\n 'list_sessions',\n 'List all sessions in a project',\n {\n project_name: z.string().describe(\"Project folder name (e.g., '-Users-young-works-myproject')\"),\n },\n async ({ project_name }) => {\n const result = await Effect.runPromise(session.listSessions(project_name))\n return {\n content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],\n }\n }\n)\n\n// Rename session\nserver.tool(\n 'rename_session',\n 'Rename a session by adding a title prefix to the first message',\n {\n project_name: z.string().describe('Project folder name'),\n session_id: z.string().describe('Session ID (filename without .jsonl)'),\n new_title: z.string().describe('New title to add as prefix'),\n },\n async ({ project_name, session_id, new_title }) => {\n const result = await Effect.runPromise(\n session.renameSession(project_name, session_id, new_title)\n )\n return {\n content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],\n }\n }\n)\n\n// Delete session\nserver.tool(\n 'delete_session',\n 'Delete a session (moves to .bak folder for recovery)',\n {\n project_name: z.string().describe('Project folder name'),\n session_id: z.string().describe('Session ID to delete'),\n },\n async ({ project_name, session_id }) => {\n const result = await Effect.runPromise(session.deleteSession(project_name, session_id))\n return {\n content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],\n }\n }\n)\n\n// Delete message\nserver.tool(\n 'delete_message',\n 'Delete a message from a session and repair the parentUuid chain',\n {\n project_name: z.string().describe('Project folder name'),\n session_id: z.string().describe('Session ID'),\n message_uuid: z.string().describe('UUID of the message to delete'),\n },\n async ({ project_name, session_id, message_uuid }) => {\n const result = await Effect.runPromise(\n session.deleteMessage(project_name, session_id, message_uuid)\n )\n return {\n content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],\n }\n }\n)\n\n// Preview cleanup\nserver.tool(\n 'preview_cleanup',\n 'Preview sessions that would be cleaned (empty and invalid API key sessions)',\n {\n project_name: z.string().optional().describe('Optional: filter by project name'),\n },\n async ({ project_name }) => {\n const result = await Effect.runPromise(session.previewCleanup(project_name))\n return {\n content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],\n }\n }\n)\n\n// Clear sessions\nserver.tool(\n 'clear_sessions',\n 'Delete all empty sessions and invalid API key sessions',\n {\n project_name: z.string().optional().describe('Optional: filter by project name'),\n clear_empty: z.boolean().default(true).describe('Clear empty sessions (default: true)'),\n clear_invalid: z\n .boolean()\n .default(true)\n .describe('Clear invalid API key sessions (default: true)'),\n },\n async ({ project_name, clear_empty, clear_invalid }) => {\n const result = await Effect.runPromise(\n session.clearSessions({\n projectName: project_name,\n clearEmpty: clear_empty,\n clearInvalid: clear_invalid,\n })\n )\n return {\n content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],\n }\n }\n)\n\n// Start GUI\nlet webServerInstance: Awaited<ReturnType<typeof startWebServer>> | null = null\n\nserver.tool(\n 'start_gui',\n 'Start the web GUI for session management and open it in browser',\n {\n port: z.number().default(5050).describe('Port to run the web server on (default: 5050)'),\n open_browser: z\n .boolean()\n .default(true)\n .describe('Whether to open browser automatically (default: true)'),\n },\n async ({ port, open_browser }) => {\n try {\n if (webServerInstance) {\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(\n {\n success: true,\n message: 'Web GUI is already running',\n url: `http://localhost:${port}`,\n },\n null,\n 2\n ),\n },\n ],\n }\n }\n\n webServerInstance = await startWebServer(port, open_browser)\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(\n {\n success: true,\n message: 'Web GUI started successfully',\n url: `http://localhost:${port}`,\n pid: process.pid,\n },\n null,\n 2\n ),\n },\n ],\n }\n } catch (error) {\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(\n {\n success: false,\n error: String(error),\n },\n null,\n 2\n ),\n },\n ],\n }\n }\n }\n)\n\n// Stop GUI\nserver.tool('stop_gui', 'Stop the web GUI server', {}, async () => {\n if (webServerInstance) {\n await stopWebServer(webServerInstance)\n webServerInstance = null\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify({ success: true, message: 'Web GUI stopped successfully' }, null, 2),\n },\n ],\n }\n }\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify({ success: true, message: 'Web GUI was not running' }, null, 2),\n },\n ],\n }\n})\n\n// Main entry\nasync function main() {\n const transport = new StdioServerTransport()\n await server.connect(transport)\n}\n\nmain().catch(console.error)\n","import { Effect, pipe, Array as A, Option as O } from 'effect'\nimport * as fs from 'node:fs/promises'\nimport * as path from 'node:path'\nimport * as os from 'node:os'\nimport type { Message, SessionMeta, Project } from './schema.js'\n\n// Get Claude sessions directory\nexport const getSessionsDir = (): string => path.join(os.homedir(), '.claude', 'projects')\n\n// List all project directories\nexport const listProjects = Effect.gen(function* () {\n const sessionsDir = getSessionsDir()\n\n const exists = yield* Effect.tryPromise(() =>\n fs\n .access(sessionsDir)\n .then(() => true)\n .catch(() => false)\n )\n\n if (!exists) {\n return [] as Project[]\n }\n\n const entries = yield* Effect.tryPromise(() => fs.readdir(sessionsDir, { withFileTypes: true }))\n\n const projects = yield* Effect.all(\n entries\n .filter((e) => e.isDirectory())\n .map((entry) =>\n Effect.gen(function* () {\n const projectPath = path.join(sessionsDir, entry.name)\n const files = yield* Effect.tryPromise(() => fs.readdir(projectPath))\n const sessionFiles = files.filter((f) => f.endsWith('.jsonl'))\n\n return {\n name: entry.name,\n path: projectPath,\n sessionCount: sessionFiles.length,\n } satisfies Project\n })\n ),\n { concurrency: 10 }\n )\n\n return projects\n})\n\n// List sessions in a project\nexport const listSessions = (projectName: string) =>\n Effect.gen(function* () {\n const projectPath = path.join(getSessionsDir(), projectName)\n const files = yield* Effect.tryPromise(() => fs.readdir(projectPath))\n const sessionFiles = files.filter((f) => f.endsWith('.jsonl'))\n\n const sessions = yield* Effect.all(\n sessionFiles.map((file) =>\n Effect.gen(function* () {\n const filePath = path.join(projectPath, file)\n const content = yield* Effect.tryPromise(() => fs.readFile(filePath, 'utf-8'))\n const lines = content.trim().split('\\n').filter(Boolean)\n const messages = lines.map((line) => JSON.parse(line) as Message)\n\n const sessionId = file.replace('.jsonl', '')\n const firstMessage = messages[0]\n const lastMessage = messages[messages.length - 1]\n\n // Extract title from first user message\n const title = pipe(\n messages,\n A.findFirst((m) => m.type === 'human'),\n O.map((m) => {\n const msg = m.message as { content?: string } | undefined\n const content = msg?.content ?? ''\n return content.slice(0, 50) + (content.length > 50 ? '...' : '')\n }),\n O.getOrElse(() => 'Untitled')\n )\n\n return {\n id: sessionId,\n projectName,\n title,\n messageCount: messages.length,\n createdAt: firstMessage?.timestamp,\n updatedAt: lastMessage?.timestamp,\n } satisfies SessionMeta\n })\n ),\n { concurrency: 10 }\n )\n\n return sessions\n })\n\n// Read session messages\nexport const readSession = (projectName: string, sessionId: string) =>\n Effect.gen(function* () {\n const filePath = path.join(getSessionsDir(), projectName, `${sessionId}.jsonl`)\n const content = yield* Effect.tryPromise(() => fs.readFile(filePath, 'utf-8'))\n const lines = content.trim().split('\\n').filter(Boolean)\n return lines.map((line) => JSON.parse(line) as Message)\n })\n\n// Delete a session\nexport const deleteSession = (projectName: string, sessionId: string) =>\n Effect.gen(function* () {\n const sessionsDir = getSessionsDir()\n const filePath = path.join(sessionsDir, projectName, `${sessionId}.jsonl`)\n\n // Create backup directory\n const backupDir = path.join(sessionsDir, projectName, '.bak')\n yield* Effect.tryPromise(() => fs.mkdir(backupDir, { recursive: true }))\n\n // Move to backup\n const backupPath = path.join(backupDir, `${sessionId}.jsonl`)\n yield* Effect.tryPromise(() => fs.rename(filePath, backupPath))\n\n return { success: true, backupPath }\n })\n\n// Rename session by adding title prefix\nexport const renameSession = (projectName: string, sessionId: string, newTitle: string) =>\n Effect.gen(function* () {\n const filePath = path.join(getSessionsDir(), projectName, `${sessionId}.jsonl`)\n const content = yield* Effect.tryPromise(() => fs.readFile(filePath, 'utf-8'))\n const lines = content.trim().split('\\n').filter(Boolean)\n\n if (lines.length === 0) {\n return { success: false, error: 'Empty session' }\n }\n\n const messages = lines.map((line) => JSON.parse(line) as Message)\n const firstMsg = messages[0]\n\n // Add title prefix to first message\n if (firstMsg && typeof firstMsg.message === 'object' && firstMsg.message !== null) {\n const msg = firstMsg.message as { content?: string }\n if (msg.content) {\n // Remove existing title prefix if any\n const cleanContent = msg.content.replace(/^\\[.*?\\]\\s*/, '')\n msg.content = `[${newTitle}] ${cleanContent}`\n }\n }\n\n const newContent = messages.map((m) => JSON.stringify(m)).join('\\n') + '\\n'\n yield* Effect.tryPromise(() => fs.writeFile(filePath, newContent, 'utf-8'))\n\n return { success: true }\n })\n\n// Delete a message from session\nexport const deleteMessage = (projectName: string, sessionId: string, messageUuid: string) =>\n Effect.gen(function* () {\n const filePath = path.join(getSessionsDir(), projectName, `${sessionId}.jsonl`)\n const content = yield* Effect.tryPromise(() => fs.readFile(filePath, 'utf-8'))\n const lines = content.trim().split('\\n').filter(Boolean)\n const messages = lines.map((line) => JSON.parse(line) as Message)\n\n const targetIndex = messages.findIndex((m) => m.uuid === messageUuid)\n if (targetIndex === -1) {\n return { success: false, error: 'Message not found' }\n }\n\n // Get the parent UUID of deleted message\n const deletedMsg = messages[targetIndex]\n const parentUuid = deletedMsg?.parentUuid\n\n // Update child message to point to deleted message's parent\n const nextMsg = messages[targetIndex + 1]\n if (nextMsg) {\n nextMsg.parentUuid = parentUuid\n }\n\n // Remove the message\n messages.splice(targetIndex, 1)\n\n const newContent = messages.map((m) => JSON.stringify(m)).join('\\n') + '\\n'\n yield* Effect.tryPromise(() => fs.writeFile(filePath, newContent, 'utf-8'))\n\n return { success: true }\n })\n\n// Preview cleanup - find empty and invalid sessions\nexport const previewCleanup = (projectName?: string) =>\n Effect.gen(function* () {\n const projects = yield* listProjects\n const targetProjects = projectName ? projects.filter((p) => p.name === projectName) : projects\n\n const results = yield* Effect.all(\n targetProjects.map((project) =>\n Effect.gen(function* () {\n const sessions = yield* listSessions(project.name)\n const emptySessions = sessions.filter((s) => s.messageCount === 0)\n const invalidSessions = sessions.filter(\n (s) => s.title?.includes('Invalid API key') || s.title?.includes('API key')\n )\n\n return {\n project: project.name,\n emptySessions,\n invalidSessions,\n }\n })\n ),\n { concurrency: 5 }\n )\n\n return results\n })\n\n// Clear sessions (empty and invalid)\nexport const clearSessions = (options: {\n projectName?: string\n clearEmpty?: boolean\n clearInvalid?: boolean\n}) =>\n Effect.gen(function* () {\n const { projectName, clearEmpty = true, clearInvalid = true } = options\n const cleanupPreview = yield* previewCleanup(projectName)\n\n let deletedCount = 0\n\n for (const result of cleanupPreview) {\n const toDelete = [\n ...(clearEmpty ? result.emptySessions : []),\n ...(clearInvalid ? result.invalidSessions : []),\n ]\n\n for (const session of toDelete) {\n yield* deleteSession(result.project, session.id)\n deletedCount++\n }\n }\n\n return { success: true, deletedCount }\n })\n"],"mappings":"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","names":["Effect","content","Effect"]}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ChildProcess } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
interface WebServer {
|
|
4
|
+
process: ChildProcess;
|
|
5
|
+
port: number;
|
|
6
|
+
}
|
|
7
|
+
declare function startWebServer(port?: number, openBrowser?: boolean): Promise<WebServer>;
|
|
8
|
+
declare function stopWebServer(server: WebServer): Promise<void>;
|
|
9
|
+
|
|
10
|
+
export { startWebServer, stopWebServer };
|
package/dist/server.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|