@claude-sessions/core 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/dist/index.d.ts +244 -0
- package/dist/index.js +810 -0
- package/dist/index.js.map +1 -0
- package/package.json +36 -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/dist/index.d.ts
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import * as effect_Cause from 'effect/Cause';
|
|
2
|
+
import { Effect } from 'effect';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Core types for Claude Code session management
|
|
6
|
+
*/
|
|
7
|
+
interface ContentItem {
|
|
8
|
+
type: string;
|
|
9
|
+
text?: string;
|
|
10
|
+
name?: string;
|
|
11
|
+
input?: unknown;
|
|
12
|
+
}
|
|
13
|
+
interface MessagePayload {
|
|
14
|
+
role?: string;
|
|
15
|
+
content?: ContentItem[] | string;
|
|
16
|
+
model?: string;
|
|
17
|
+
}
|
|
18
|
+
interface Message {
|
|
19
|
+
uuid: string;
|
|
20
|
+
parentUuid?: string | null;
|
|
21
|
+
type: string;
|
|
22
|
+
message?: MessagePayload;
|
|
23
|
+
timestamp?: string;
|
|
24
|
+
sessionId?: string;
|
|
25
|
+
isCompactSummary?: boolean;
|
|
26
|
+
customTitle?: string;
|
|
27
|
+
}
|
|
28
|
+
interface SessionMeta {
|
|
29
|
+
id: string;
|
|
30
|
+
projectName: string;
|
|
31
|
+
title?: string;
|
|
32
|
+
messageCount: number;
|
|
33
|
+
createdAt?: string;
|
|
34
|
+
updatedAt?: string;
|
|
35
|
+
}
|
|
36
|
+
interface Project {
|
|
37
|
+
name: string;
|
|
38
|
+
displayName: string;
|
|
39
|
+
path: string;
|
|
40
|
+
sessionCount: number;
|
|
41
|
+
}
|
|
42
|
+
interface TodoItem {
|
|
43
|
+
content: string;
|
|
44
|
+
status: 'pending' | 'in_progress' | 'completed';
|
|
45
|
+
activeForm?: string;
|
|
46
|
+
}
|
|
47
|
+
interface SessionTodos {
|
|
48
|
+
sessionId: string;
|
|
49
|
+
sessionTodos: TodoItem[];
|
|
50
|
+
agentTodos: {
|
|
51
|
+
agentId: string;
|
|
52
|
+
todos: TodoItem[];
|
|
53
|
+
}[];
|
|
54
|
+
hasTodos: boolean;
|
|
55
|
+
}
|
|
56
|
+
interface FileChange {
|
|
57
|
+
path: string;
|
|
58
|
+
action: 'created' | 'modified' | 'deleted';
|
|
59
|
+
timestamp?: string;
|
|
60
|
+
messageUuid?: string;
|
|
61
|
+
}
|
|
62
|
+
interface SessionFilesSummary {
|
|
63
|
+
sessionId: string;
|
|
64
|
+
projectName: string;
|
|
65
|
+
files: FileChange[];
|
|
66
|
+
totalChanges: number;
|
|
67
|
+
}
|
|
68
|
+
interface DeleteSessionResult {
|
|
69
|
+
success: boolean;
|
|
70
|
+
backupPath?: string;
|
|
71
|
+
deletedAgents: number;
|
|
72
|
+
deletedTodos?: number;
|
|
73
|
+
}
|
|
74
|
+
interface RenameSessionResult {
|
|
75
|
+
success: boolean;
|
|
76
|
+
error?: string;
|
|
77
|
+
}
|
|
78
|
+
interface SplitSessionResult {
|
|
79
|
+
success: boolean;
|
|
80
|
+
newSessionId?: string;
|
|
81
|
+
newSessionPath?: string;
|
|
82
|
+
movedMessageCount?: number;
|
|
83
|
+
duplicatedSummary?: boolean;
|
|
84
|
+
error?: string;
|
|
85
|
+
}
|
|
86
|
+
interface MoveSessionResult {
|
|
87
|
+
success: boolean;
|
|
88
|
+
error?: string;
|
|
89
|
+
}
|
|
90
|
+
interface ClearSessionsResult {
|
|
91
|
+
success: boolean;
|
|
92
|
+
deletedCount: number;
|
|
93
|
+
removedMessageCount?: number;
|
|
94
|
+
deletedOrphanAgentCount?: number;
|
|
95
|
+
deletedOrphanTodoCount?: number;
|
|
96
|
+
}
|
|
97
|
+
interface CleanupPreview {
|
|
98
|
+
project: string;
|
|
99
|
+
emptySessions: SessionMeta[];
|
|
100
|
+
invalidSessions: SessionMeta[];
|
|
101
|
+
emptyWithTodosCount?: number;
|
|
102
|
+
orphanAgentCount?: number;
|
|
103
|
+
orphanTodoCount?: number;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
declare const getSessionsDir: () => string;
|
|
107
|
+
declare const getTodosDir: () => string;
|
|
108
|
+
declare const folderNameToDisplayPath: (folderName: string) => string;
|
|
109
|
+
declare const displayPathToFolderName: (displayPath: string) => string;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Utility functions for message processing
|
|
113
|
+
*/
|
|
114
|
+
|
|
115
|
+
declare const extractTextContent: (message: MessagePayload | undefined) => string;
|
|
116
|
+
declare const extractTitle: (text: string) => string;
|
|
117
|
+
declare const isInvalidApiKeyMessage: (msg: Message) => boolean;
|
|
118
|
+
declare const isContinuationSummary: (msg: Record<string, unknown>) => boolean;
|
|
119
|
+
|
|
120
|
+
declare const findLinkedAgents: (projectName: string, sessionId: string) => Effect.Effect<string[], effect_Cause.UnknownException, never>;
|
|
121
|
+
declare const findOrphanAgents: (projectName: string) => Effect.Effect<{
|
|
122
|
+
agentId: string;
|
|
123
|
+
sessionId: string;
|
|
124
|
+
}[], effect_Cause.UnknownException, never>;
|
|
125
|
+
declare const deleteOrphanAgents: (projectName: string) => Effect.Effect<{
|
|
126
|
+
success: boolean;
|
|
127
|
+
deletedAgents: string[];
|
|
128
|
+
count: number;
|
|
129
|
+
}, effect_Cause.UnknownException, never>;
|
|
130
|
+
|
|
131
|
+
declare const findLinkedTodos: (sessionId: string, agentIds: string[]) => Effect.Effect<{
|
|
132
|
+
sessionId: string;
|
|
133
|
+
sessionTodos: TodoItem[];
|
|
134
|
+
agentTodos: {
|
|
135
|
+
agentId: string;
|
|
136
|
+
todos: TodoItem[];
|
|
137
|
+
}[];
|
|
138
|
+
hasTodos: boolean;
|
|
139
|
+
}, effect_Cause.UnknownException, never>;
|
|
140
|
+
declare const sessionHasTodos: (sessionId: string, agentIds: string[]) => Effect.Effect<boolean, effect_Cause.UnknownException, never>;
|
|
141
|
+
declare const deleteLinkedTodos: (sessionId: string, agentIds: string[]) => Effect.Effect<{
|
|
142
|
+
deletedCount: number;
|
|
143
|
+
}, effect_Cause.UnknownException, never>;
|
|
144
|
+
declare const findOrphanTodos: () => Effect.Effect<string[], effect_Cause.UnknownException, never>;
|
|
145
|
+
declare const deleteOrphanTodos: () => Effect.Effect<{
|
|
146
|
+
success: boolean;
|
|
147
|
+
deletedCount: number;
|
|
148
|
+
}, effect_Cause.UnknownException, never>;
|
|
149
|
+
|
|
150
|
+
declare const listProjects: Effect.Effect<Project[], effect_Cause.UnknownException, never>;
|
|
151
|
+
declare const listSessions: (projectName: string) => Effect.Effect<{
|
|
152
|
+
id: string;
|
|
153
|
+
projectName: string;
|
|
154
|
+
title: string;
|
|
155
|
+
messageCount: number;
|
|
156
|
+
createdAt: string | undefined;
|
|
157
|
+
updatedAt: string | undefined;
|
|
158
|
+
}[], effect_Cause.UnknownException, never>;
|
|
159
|
+
declare const readSession: (projectName: string, sessionId: string) => Effect.Effect<Message[], effect_Cause.UnknownException, never>;
|
|
160
|
+
declare const deleteMessage: (projectName: string, sessionId: string, messageUuid: string) => Effect.Effect<{
|
|
161
|
+
success: boolean;
|
|
162
|
+
error: string;
|
|
163
|
+
} | {
|
|
164
|
+
success: boolean;
|
|
165
|
+
error?: undefined;
|
|
166
|
+
}, effect_Cause.UnknownException, never>;
|
|
167
|
+
declare const deleteSession: (projectName: string, sessionId: string) => Effect.Effect<{
|
|
168
|
+
success: true;
|
|
169
|
+
deletedAgents: number;
|
|
170
|
+
backupPath?: undefined;
|
|
171
|
+
deletedTodos?: undefined;
|
|
172
|
+
} | {
|
|
173
|
+
success: true;
|
|
174
|
+
backupPath: string;
|
|
175
|
+
deletedAgents: number;
|
|
176
|
+
deletedTodos: number;
|
|
177
|
+
}, effect_Cause.UnknownException, never>;
|
|
178
|
+
declare const renameSession: (projectName: string, sessionId: string, newTitle: string) => Effect.Effect<{
|
|
179
|
+
success: false;
|
|
180
|
+
error: string;
|
|
181
|
+
} | {
|
|
182
|
+
success: true;
|
|
183
|
+
error?: undefined;
|
|
184
|
+
}, effect_Cause.UnknownException, never>;
|
|
185
|
+
declare const getSessionFiles: (projectName: string, sessionId: string) => Effect.Effect<{
|
|
186
|
+
sessionId: string;
|
|
187
|
+
projectName: string;
|
|
188
|
+
files: FileChange[];
|
|
189
|
+
totalChanges: number;
|
|
190
|
+
}, effect_Cause.UnknownException, never>;
|
|
191
|
+
declare const moveSession: (sourceProject: string, sessionId: string, targetProject: string) => Effect.Effect<MoveSessionResult, Error>;
|
|
192
|
+
declare const splitSession: (projectName: string, sessionId: string, splitAtMessageUuid: string) => Effect.Effect<{
|
|
193
|
+
success: false;
|
|
194
|
+
error: string;
|
|
195
|
+
newSessionId?: undefined;
|
|
196
|
+
newSessionPath?: undefined;
|
|
197
|
+
movedMessageCount?: undefined;
|
|
198
|
+
duplicatedSummary?: undefined;
|
|
199
|
+
} | {
|
|
200
|
+
success: true;
|
|
201
|
+
newSessionId: `${string}-${string}-${string}-${string}-${string}`;
|
|
202
|
+
newSessionPath: string;
|
|
203
|
+
movedMessageCount: number;
|
|
204
|
+
duplicatedSummary: boolean;
|
|
205
|
+
error?: undefined;
|
|
206
|
+
}, effect_Cause.UnknownException, never>;
|
|
207
|
+
declare const previewCleanup: (projectName?: string) => Effect.Effect<{
|
|
208
|
+
project: string;
|
|
209
|
+
emptySessions: {
|
|
210
|
+
id: string;
|
|
211
|
+
projectName: string;
|
|
212
|
+
title: string;
|
|
213
|
+
messageCount: number;
|
|
214
|
+
createdAt: string | undefined;
|
|
215
|
+
updatedAt: string | undefined;
|
|
216
|
+
}[];
|
|
217
|
+
invalidSessions: {
|
|
218
|
+
id: string;
|
|
219
|
+
projectName: string;
|
|
220
|
+
title: string;
|
|
221
|
+
messageCount: number;
|
|
222
|
+
createdAt: string | undefined;
|
|
223
|
+
updatedAt: string | undefined;
|
|
224
|
+
}[];
|
|
225
|
+
emptyWithTodosCount: number;
|
|
226
|
+
orphanAgentCount: number;
|
|
227
|
+
orphanTodoCount: number;
|
|
228
|
+
}[], effect_Cause.UnknownException, never>;
|
|
229
|
+
declare const clearSessions: (options: {
|
|
230
|
+
projectName?: string;
|
|
231
|
+
clearEmpty?: boolean;
|
|
232
|
+
clearInvalid?: boolean;
|
|
233
|
+
skipWithTodos?: boolean;
|
|
234
|
+
clearOrphanAgents?: boolean;
|
|
235
|
+
clearOrphanTodos?: boolean;
|
|
236
|
+
}) => Effect.Effect<{
|
|
237
|
+
success: true;
|
|
238
|
+
deletedCount: number;
|
|
239
|
+
removedMessageCount: number;
|
|
240
|
+
deletedOrphanAgentCount: number;
|
|
241
|
+
deletedOrphanTodoCount: number;
|
|
242
|
+
}, effect_Cause.UnknownException, never>;
|
|
243
|
+
|
|
244
|
+
export { type CleanupPreview, type ClearSessionsResult, type ContentItem, type DeleteSessionResult, type FileChange, type Message, type MessagePayload, type MoveSessionResult, type Project, type RenameSessionResult, type SessionFilesSummary, type SessionMeta, type SessionTodos, type SplitSessionResult, type TodoItem, clearSessions, deleteLinkedTodos, deleteMessage, deleteOrphanAgents, deleteOrphanTodos, deleteSession, displayPathToFolderName, extractTextContent, extractTitle, findLinkedAgents, findLinkedTodos, findOrphanAgents, findOrphanTodos, folderNameToDisplayPath, getSessionFiles, getSessionsDir, getTodosDir, isContinuationSummary, isInvalidApiKeyMessage, listProjects, listSessions, moveSession, previewCleanup, readSession, renameSession, sessionHasTodos, splitSession };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,810 @@
|
|
|
1
|
+
// src/paths.ts
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import * as os from "os";
|
|
4
|
+
var getSessionsDir = () => path.join(os.homedir(), ".claude", "projects");
|
|
5
|
+
var getTodosDir = () => path.join(os.homedir(), ".claude", "todos");
|
|
6
|
+
var folderNameToDisplayPath = (folderName) => {
|
|
7
|
+
return folderName.replace(/^-/, "/").replace(/--/g, "/.").replace(/-/g, "/");
|
|
8
|
+
};
|
|
9
|
+
var displayPathToFolderName = (displayPath) => {
|
|
10
|
+
return displayPath.replace(/^\//g, "-").replace(/\/\./g, "--").replace(/\//g, "-");
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// src/utils.ts
|
|
14
|
+
var extractTextContent = (message) => {
|
|
15
|
+
if (!message) return "";
|
|
16
|
+
const content = message.content;
|
|
17
|
+
if (!content) return "";
|
|
18
|
+
if (typeof content === "string") return content;
|
|
19
|
+
if (Array.isArray(content)) {
|
|
20
|
+
return content.filter((item) => typeof item === "object" && item?.type === "text").map((item) => item.text ?? "").join("");
|
|
21
|
+
}
|
|
22
|
+
return "";
|
|
23
|
+
};
|
|
24
|
+
var extractTitle = (text) => {
|
|
25
|
+
if (!text) return "Untitled";
|
|
26
|
+
let cleaned = text.replace(/<ide_[^>]*>[\s\S]*?<\/ide_[^>]*>/g, "").trim();
|
|
27
|
+
if (!cleaned) return "Untitled";
|
|
28
|
+
if (cleaned.includes("\n\n")) {
|
|
29
|
+
cleaned = cleaned.split("\n\n")[0];
|
|
30
|
+
} else if (cleaned.includes("\n")) {
|
|
31
|
+
cleaned = cleaned.split("\n")[0];
|
|
32
|
+
}
|
|
33
|
+
if (cleaned.length > 100) {
|
|
34
|
+
return cleaned.slice(0, 100) + "...";
|
|
35
|
+
}
|
|
36
|
+
return cleaned || "Untitled";
|
|
37
|
+
};
|
|
38
|
+
var isInvalidApiKeyMessage = (msg) => {
|
|
39
|
+
const text = extractTextContent(msg.message);
|
|
40
|
+
return text.includes("Invalid API key");
|
|
41
|
+
};
|
|
42
|
+
var isContinuationSummary = (msg) => {
|
|
43
|
+
if (msg.isCompactSummary === true) return true;
|
|
44
|
+
if (msg.type !== "user") return false;
|
|
45
|
+
const message = msg.message;
|
|
46
|
+
const content = message?.content ?? "";
|
|
47
|
+
return content.startsWith("This session is being continued from");
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// src/agents.ts
|
|
51
|
+
import { Effect } from "effect";
|
|
52
|
+
import * as fs from "fs/promises";
|
|
53
|
+
import * as path2 from "path";
|
|
54
|
+
var findLinkedAgents = (projectName, sessionId) => Effect.gen(function* () {
|
|
55
|
+
const projectPath = path2.join(getSessionsDir(), projectName);
|
|
56
|
+
const files = yield* Effect.tryPromise(() => fs.readdir(projectPath));
|
|
57
|
+
const agentFiles = files.filter((f) => f.startsWith("agent-") && f.endsWith(".jsonl"));
|
|
58
|
+
const linkedAgents = [];
|
|
59
|
+
for (const agentFile of agentFiles) {
|
|
60
|
+
const filePath = path2.join(projectPath, agentFile);
|
|
61
|
+
const content = yield* Effect.tryPromise(() => fs.readFile(filePath, "utf-8"));
|
|
62
|
+
const firstLine = content.split("\n")[0];
|
|
63
|
+
if (firstLine) {
|
|
64
|
+
try {
|
|
65
|
+
const parsed = JSON.parse(firstLine);
|
|
66
|
+
if (parsed.sessionId === sessionId) {
|
|
67
|
+
linkedAgents.push(agentFile.replace(".jsonl", ""));
|
|
68
|
+
}
|
|
69
|
+
} catch {
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return linkedAgents;
|
|
74
|
+
});
|
|
75
|
+
var findOrphanAgents = (projectName) => Effect.gen(function* () {
|
|
76
|
+
const projectPath = path2.join(getSessionsDir(), projectName);
|
|
77
|
+
const files = yield* Effect.tryPromise(() => fs.readdir(projectPath));
|
|
78
|
+
const sessionIds = new Set(
|
|
79
|
+
files.filter((f) => !f.startsWith("agent-") && f.endsWith(".jsonl")).map((f) => f.replace(".jsonl", ""))
|
|
80
|
+
);
|
|
81
|
+
const agentFiles = files.filter((f) => f.startsWith("agent-") && f.endsWith(".jsonl"));
|
|
82
|
+
const orphanAgents = [];
|
|
83
|
+
for (const agentFile of agentFiles) {
|
|
84
|
+
const filePath = path2.join(projectPath, agentFile);
|
|
85
|
+
const content = yield* Effect.tryPromise(() => fs.readFile(filePath, "utf-8"));
|
|
86
|
+
const firstLine = content.split("\n")[0];
|
|
87
|
+
if (firstLine) {
|
|
88
|
+
try {
|
|
89
|
+
const parsed = JSON.parse(firstLine);
|
|
90
|
+
if (parsed.sessionId && !sessionIds.has(parsed.sessionId)) {
|
|
91
|
+
orphanAgents.push({
|
|
92
|
+
agentId: agentFile.replace(".jsonl", ""),
|
|
93
|
+
sessionId: parsed.sessionId
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
} catch {
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return orphanAgents;
|
|
101
|
+
});
|
|
102
|
+
var deleteOrphanAgents = (projectName) => Effect.gen(function* () {
|
|
103
|
+
const projectPath = path2.join(getSessionsDir(), projectName);
|
|
104
|
+
const orphans = yield* findOrphanAgents(projectName);
|
|
105
|
+
const backupDir = path2.join(projectPath, ".bak");
|
|
106
|
+
yield* Effect.tryPromise(() => fs.mkdir(backupDir, { recursive: true }));
|
|
107
|
+
const deletedAgents = [];
|
|
108
|
+
for (const orphan of orphans) {
|
|
109
|
+
const agentPath = path2.join(projectPath, `${orphan.agentId}.jsonl`);
|
|
110
|
+
const agentBackupPath = path2.join(backupDir, `${orphan.agentId}.jsonl`);
|
|
111
|
+
yield* Effect.tryPromise(() => fs.rename(agentPath, agentBackupPath));
|
|
112
|
+
deletedAgents.push(orphan.agentId);
|
|
113
|
+
}
|
|
114
|
+
return { success: true, deletedAgents, count: deletedAgents.length };
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// src/todos.ts
|
|
118
|
+
import { Effect as Effect2 } from "effect";
|
|
119
|
+
import * as fs2 from "fs/promises";
|
|
120
|
+
import * as path3 from "path";
|
|
121
|
+
var findLinkedTodos = (sessionId, agentIds) => Effect2.gen(function* () {
|
|
122
|
+
const todosDir = getTodosDir();
|
|
123
|
+
const exists = yield* Effect2.tryPromise(
|
|
124
|
+
() => fs2.access(todosDir).then(() => true).catch(() => false)
|
|
125
|
+
);
|
|
126
|
+
if (!exists) {
|
|
127
|
+
return {
|
|
128
|
+
sessionId,
|
|
129
|
+
sessionTodos: [],
|
|
130
|
+
agentTodos: [],
|
|
131
|
+
hasTodos: false
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
const sessionTodoPath = path3.join(todosDir, `${sessionId}.json`);
|
|
135
|
+
let sessionTodos = [];
|
|
136
|
+
const sessionTodoExists = yield* Effect2.tryPromise(
|
|
137
|
+
() => fs2.access(sessionTodoPath).then(() => true).catch(() => false)
|
|
138
|
+
);
|
|
139
|
+
if (sessionTodoExists) {
|
|
140
|
+
const content = yield* Effect2.tryPromise(() => fs2.readFile(sessionTodoPath, "utf-8"));
|
|
141
|
+
try {
|
|
142
|
+
sessionTodos = JSON.parse(content);
|
|
143
|
+
} catch {
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const agentTodos = [];
|
|
147
|
+
for (const agentId of agentIds) {
|
|
148
|
+
const shortAgentId = agentId.replace("agent-", "");
|
|
149
|
+
const agentTodoPath = path3.join(todosDir, `${sessionId}-agent-${shortAgentId}.json`);
|
|
150
|
+
const agentTodoExists = yield* Effect2.tryPromise(
|
|
151
|
+
() => fs2.access(agentTodoPath).then(() => true).catch(() => false)
|
|
152
|
+
);
|
|
153
|
+
if (agentTodoExists) {
|
|
154
|
+
const content = yield* Effect2.tryPromise(() => fs2.readFile(agentTodoPath, "utf-8"));
|
|
155
|
+
try {
|
|
156
|
+
const todos = JSON.parse(content);
|
|
157
|
+
agentTodos.push({ agentId, todos });
|
|
158
|
+
} catch {
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const hasTodos = sessionTodos.length > 0 || agentTodos.some((at) => at.todos.length > 0);
|
|
163
|
+
return {
|
|
164
|
+
sessionId,
|
|
165
|
+
sessionTodos,
|
|
166
|
+
agentTodos,
|
|
167
|
+
hasTodos
|
|
168
|
+
};
|
|
169
|
+
});
|
|
170
|
+
var sessionHasTodos = (sessionId, agentIds) => Effect2.gen(function* () {
|
|
171
|
+
const todosDir = getTodosDir();
|
|
172
|
+
const exists = yield* Effect2.tryPromise(
|
|
173
|
+
() => fs2.access(todosDir).then(() => true).catch(() => false)
|
|
174
|
+
);
|
|
175
|
+
if (!exists) return false;
|
|
176
|
+
const sessionTodoPath = path3.join(todosDir, `${sessionId}.json`);
|
|
177
|
+
const sessionTodoExists = yield* Effect2.tryPromise(
|
|
178
|
+
() => fs2.access(sessionTodoPath).then(() => true).catch(() => false)
|
|
179
|
+
);
|
|
180
|
+
if (sessionTodoExists) {
|
|
181
|
+
const content = yield* Effect2.tryPromise(() => fs2.readFile(sessionTodoPath, "utf-8"));
|
|
182
|
+
try {
|
|
183
|
+
const todos = JSON.parse(content);
|
|
184
|
+
if (todos.length > 0) return true;
|
|
185
|
+
} catch {
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
for (const agentId of agentIds) {
|
|
189
|
+
const shortAgentId = agentId.replace("agent-", "");
|
|
190
|
+
const agentTodoPath = path3.join(todosDir, `${sessionId}-agent-${shortAgentId}.json`);
|
|
191
|
+
const agentTodoExists = yield* Effect2.tryPromise(
|
|
192
|
+
() => fs2.access(agentTodoPath).then(() => true).catch(() => false)
|
|
193
|
+
);
|
|
194
|
+
if (agentTodoExists) {
|
|
195
|
+
const content = yield* Effect2.tryPromise(() => fs2.readFile(agentTodoPath, "utf-8"));
|
|
196
|
+
try {
|
|
197
|
+
const todos = JSON.parse(content);
|
|
198
|
+
if (todos.length > 0) return true;
|
|
199
|
+
} catch {
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return false;
|
|
204
|
+
});
|
|
205
|
+
var deleteLinkedTodos = (sessionId, agentIds) => Effect2.gen(function* () {
|
|
206
|
+
const todosDir = getTodosDir();
|
|
207
|
+
const exists = yield* Effect2.tryPromise(
|
|
208
|
+
() => fs2.access(todosDir).then(() => true).catch(() => false)
|
|
209
|
+
);
|
|
210
|
+
if (!exists) return { deletedCount: 0 };
|
|
211
|
+
const backupDir = path3.join(todosDir, ".bak");
|
|
212
|
+
yield* Effect2.tryPromise(() => fs2.mkdir(backupDir, { recursive: true }));
|
|
213
|
+
let deletedCount = 0;
|
|
214
|
+
const sessionTodoPath = path3.join(todosDir, `${sessionId}.json`);
|
|
215
|
+
const sessionTodoExists = yield* Effect2.tryPromise(
|
|
216
|
+
() => fs2.access(sessionTodoPath).then(() => true).catch(() => false)
|
|
217
|
+
);
|
|
218
|
+
if (sessionTodoExists) {
|
|
219
|
+
const backupPath = path3.join(backupDir, `${sessionId}.json`);
|
|
220
|
+
yield* Effect2.tryPromise(() => fs2.rename(sessionTodoPath, backupPath));
|
|
221
|
+
deletedCount++;
|
|
222
|
+
}
|
|
223
|
+
for (const agentId of agentIds) {
|
|
224
|
+
const shortAgentId = agentId.replace("agent-", "");
|
|
225
|
+
const agentTodoPath = path3.join(todosDir, `${sessionId}-agent-${shortAgentId}.json`);
|
|
226
|
+
const agentTodoExists = yield* Effect2.tryPromise(
|
|
227
|
+
() => fs2.access(agentTodoPath).then(() => true).catch(() => false)
|
|
228
|
+
);
|
|
229
|
+
if (agentTodoExists) {
|
|
230
|
+
const backupPath = path3.join(backupDir, `${sessionId}-agent-${shortAgentId}.json`);
|
|
231
|
+
yield* Effect2.tryPromise(() => fs2.rename(agentTodoPath, backupPath));
|
|
232
|
+
deletedCount++;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return { deletedCount };
|
|
236
|
+
});
|
|
237
|
+
var findOrphanTodos = () => Effect2.gen(function* () {
|
|
238
|
+
const todosDir = getTodosDir();
|
|
239
|
+
const sessionsDir = getSessionsDir();
|
|
240
|
+
const [todosExists, sessionsExists] = yield* Effect2.all([
|
|
241
|
+
Effect2.tryPromise(
|
|
242
|
+
() => fs2.access(todosDir).then(() => true).catch(() => false)
|
|
243
|
+
),
|
|
244
|
+
Effect2.tryPromise(
|
|
245
|
+
() => fs2.access(sessionsDir).then(() => true).catch(() => false)
|
|
246
|
+
)
|
|
247
|
+
]);
|
|
248
|
+
if (!todosExists || !sessionsExists) return [];
|
|
249
|
+
const todoFiles = yield* Effect2.tryPromise(() => fs2.readdir(todosDir));
|
|
250
|
+
const jsonFiles = todoFiles.filter((f) => f.endsWith(".json"));
|
|
251
|
+
const validSessionIds = /* @__PURE__ */ new Set();
|
|
252
|
+
const projectEntries = yield* Effect2.tryPromise(
|
|
253
|
+
() => fs2.readdir(sessionsDir, { withFileTypes: true })
|
|
254
|
+
);
|
|
255
|
+
for (const entry of projectEntries) {
|
|
256
|
+
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
257
|
+
const projectPath = path3.join(sessionsDir, entry.name);
|
|
258
|
+
const files = yield* Effect2.tryPromise(() => fs2.readdir(projectPath));
|
|
259
|
+
for (const f of files) {
|
|
260
|
+
if (f.endsWith(".jsonl") && !f.startsWith("agent-")) {
|
|
261
|
+
validSessionIds.add(f.replace(".jsonl", ""));
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
const orphans = [];
|
|
266
|
+
for (const todoFile of jsonFiles) {
|
|
267
|
+
const match = todoFile.match(/^([a-f0-9-]+)(?:-agent-[a-f0-9]+)?\.json$/);
|
|
268
|
+
if (match) {
|
|
269
|
+
const sessionId = match[1];
|
|
270
|
+
if (!validSessionIds.has(sessionId)) {
|
|
271
|
+
orphans.push(todoFile);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return orphans;
|
|
276
|
+
});
|
|
277
|
+
var deleteOrphanTodos = () => Effect2.gen(function* () {
|
|
278
|
+
const todosDir = getTodosDir();
|
|
279
|
+
const orphans = yield* findOrphanTodos();
|
|
280
|
+
if (orphans.length === 0) return { success: true, deletedCount: 0 };
|
|
281
|
+
const backupDir = path3.join(todosDir, ".bak");
|
|
282
|
+
yield* Effect2.tryPromise(() => fs2.mkdir(backupDir, { recursive: true }));
|
|
283
|
+
let deletedCount = 0;
|
|
284
|
+
for (const orphan of orphans) {
|
|
285
|
+
const filePath = path3.join(todosDir, orphan);
|
|
286
|
+
const backupPath = path3.join(backupDir, orphan);
|
|
287
|
+
yield* Effect2.tryPromise(() => fs2.rename(filePath, backupPath));
|
|
288
|
+
deletedCount++;
|
|
289
|
+
}
|
|
290
|
+
return { success: true, deletedCount };
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// src/session.ts
|
|
294
|
+
import { Effect as Effect3, pipe, Array as A, Option as O } from "effect";
|
|
295
|
+
import * as fs3 from "fs/promises";
|
|
296
|
+
import * as path4 from "path";
|
|
297
|
+
var listProjects = Effect3.gen(function* () {
|
|
298
|
+
const sessionsDir = getSessionsDir();
|
|
299
|
+
const exists = yield* Effect3.tryPromise(
|
|
300
|
+
() => fs3.access(sessionsDir).then(() => true).catch(() => false)
|
|
301
|
+
);
|
|
302
|
+
if (!exists) {
|
|
303
|
+
return [];
|
|
304
|
+
}
|
|
305
|
+
const entries = yield* Effect3.tryPromise(() => fs3.readdir(sessionsDir, { withFileTypes: true }));
|
|
306
|
+
const projects = yield* Effect3.all(
|
|
307
|
+
entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map(
|
|
308
|
+
(entry) => Effect3.gen(function* () {
|
|
309
|
+
const projectPath = path4.join(sessionsDir, entry.name);
|
|
310
|
+
const files = yield* Effect3.tryPromise(() => fs3.readdir(projectPath));
|
|
311
|
+
const sessionFiles = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
312
|
+
return {
|
|
313
|
+
name: entry.name,
|
|
314
|
+
displayName: folderNameToDisplayPath(entry.name),
|
|
315
|
+
path: projectPath,
|
|
316
|
+
sessionCount: sessionFiles.length
|
|
317
|
+
};
|
|
318
|
+
})
|
|
319
|
+
),
|
|
320
|
+
{ concurrency: 10 }
|
|
321
|
+
);
|
|
322
|
+
return projects;
|
|
323
|
+
});
|
|
324
|
+
var listSessions = (projectName) => Effect3.gen(function* () {
|
|
325
|
+
const projectPath = path4.join(getSessionsDir(), projectName);
|
|
326
|
+
const files = yield* Effect3.tryPromise(() => fs3.readdir(projectPath));
|
|
327
|
+
const sessionFiles = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
328
|
+
const sessions = yield* Effect3.all(
|
|
329
|
+
sessionFiles.map(
|
|
330
|
+
(file) => Effect3.gen(function* () {
|
|
331
|
+
const filePath = path4.join(projectPath, file);
|
|
332
|
+
const content = yield* Effect3.tryPromise(() => fs3.readFile(filePath, "utf-8"));
|
|
333
|
+
const lines = content.trim().split("\n").filter(Boolean);
|
|
334
|
+
const messages = lines.map((line) => JSON.parse(line));
|
|
335
|
+
const sessionId = file.replace(".jsonl", "");
|
|
336
|
+
const userAssistantMessages = messages.filter(
|
|
337
|
+
(m) => m.type === "user" || m.type === "assistant"
|
|
338
|
+
);
|
|
339
|
+
const hasSummary = messages.some((m) => m.type === "summary");
|
|
340
|
+
const firstMessage = userAssistantMessages[0];
|
|
341
|
+
const lastMessage = userAssistantMessages[userAssistantMessages.length - 1];
|
|
342
|
+
const title = pipe(
|
|
343
|
+
messages,
|
|
344
|
+
A.findFirst((m) => m.type === "user"),
|
|
345
|
+
O.map((m) => {
|
|
346
|
+
const text = extractTextContent(m.message);
|
|
347
|
+
return extractTitle(text);
|
|
348
|
+
}),
|
|
349
|
+
O.getOrElse(() => hasSummary ? "[Summary Only]" : `Session ${sessionId.slice(0, 8)}`)
|
|
350
|
+
);
|
|
351
|
+
return {
|
|
352
|
+
id: sessionId,
|
|
353
|
+
projectName,
|
|
354
|
+
title,
|
|
355
|
+
// If session has summary but no user/assistant messages, count as 1
|
|
356
|
+
messageCount: userAssistantMessages.length > 0 ? userAssistantMessages.length : hasSummary ? 1 : 0,
|
|
357
|
+
createdAt: firstMessage?.timestamp,
|
|
358
|
+
updatedAt: lastMessage?.timestamp
|
|
359
|
+
};
|
|
360
|
+
})
|
|
361
|
+
),
|
|
362
|
+
{ concurrency: 10 }
|
|
363
|
+
);
|
|
364
|
+
return sessions.sort((a, b) => {
|
|
365
|
+
const dateA = a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
|
|
366
|
+
const dateB = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
|
|
367
|
+
return dateB - dateA;
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
var readSession = (projectName, sessionId) => Effect3.gen(function* () {
|
|
371
|
+
const filePath = path4.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
|
|
372
|
+
const content = yield* Effect3.tryPromise(() => fs3.readFile(filePath, "utf-8"));
|
|
373
|
+
const lines = content.trim().split("\n").filter(Boolean);
|
|
374
|
+
return lines.map((line) => JSON.parse(line));
|
|
375
|
+
});
|
|
376
|
+
var deleteMessage = (projectName, sessionId, messageUuid) => Effect3.gen(function* () {
|
|
377
|
+
const filePath = path4.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
|
|
378
|
+
const content = yield* Effect3.tryPromise(() => fs3.readFile(filePath, "utf-8"));
|
|
379
|
+
const lines = content.trim().split("\n").filter(Boolean);
|
|
380
|
+
const messages = lines.map((line) => JSON.parse(line));
|
|
381
|
+
const targetIndex = messages.findIndex(
|
|
382
|
+
(m) => m.uuid === messageUuid || m.messageId === messageUuid
|
|
383
|
+
);
|
|
384
|
+
if (targetIndex === -1) {
|
|
385
|
+
return { success: false, error: "Message not found" };
|
|
386
|
+
}
|
|
387
|
+
const deletedMsg = messages[targetIndex];
|
|
388
|
+
const deletedUuid = deletedMsg?.uuid ?? deletedMsg?.messageId;
|
|
389
|
+
const parentUuid = deletedMsg?.parentUuid;
|
|
390
|
+
for (const msg of messages) {
|
|
391
|
+
if (msg.parentUuid === deletedUuid) {
|
|
392
|
+
msg.parentUuid = parentUuid;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
messages.splice(targetIndex, 1);
|
|
396
|
+
const newContent = messages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
397
|
+
yield* Effect3.tryPromise(() => fs3.writeFile(filePath, newContent, "utf-8"));
|
|
398
|
+
return { success: true };
|
|
399
|
+
});
|
|
400
|
+
var deleteSession = (projectName, sessionId) => Effect3.gen(function* () {
|
|
401
|
+
const sessionsDir = getSessionsDir();
|
|
402
|
+
const projectPath = path4.join(sessionsDir, projectName);
|
|
403
|
+
const filePath = path4.join(projectPath, `${sessionId}.jsonl`);
|
|
404
|
+
const linkedAgents = yield* findLinkedAgents(projectName, sessionId);
|
|
405
|
+
const stat2 = yield* Effect3.tryPromise(() => fs3.stat(filePath));
|
|
406
|
+
if (stat2.size === 0) {
|
|
407
|
+
yield* Effect3.tryPromise(() => fs3.unlink(filePath));
|
|
408
|
+
const agentBackupDir2 = path4.join(projectPath, ".bak");
|
|
409
|
+
yield* Effect3.tryPromise(() => fs3.mkdir(agentBackupDir2, { recursive: true }));
|
|
410
|
+
for (const agentId of linkedAgents) {
|
|
411
|
+
const agentPath = path4.join(projectPath, `${agentId}.jsonl`);
|
|
412
|
+
const agentBackupPath = path4.join(agentBackupDir2, `${agentId}.jsonl`);
|
|
413
|
+
yield* Effect3.tryPromise(() => fs3.rename(agentPath, agentBackupPath).catch(() => {
|
|
414
|
+
}));
|
|
415
|
+
}
|
|
416
|
+
yield* deleteLinkedTodos(sessionId, linkedAgents);
|
|
417
|
+
return { success: true, deletedAgents: linkedAgents.length };
|
|
418
|
+
}
|
|
419
|
+
const backupDir = path4.join(sessionsDir, ".bak");
|
|
420
|
+
yield* Effect3.tryPromise(() => fs3.mkdir(backupDir, { recursive: true }));
|
|
421
|
+
const agentBackupDir = path4.join(projectPath, ".bak");
|
|
422
|
+
yield* Effect3.tryPromise(() => fs3.mkdir(agentBackupDir, { recursive: true }));
|
|
423
|
+
for (const agentId of linkedAgents) {
|
|
424
|
+
const agentPath = path4.join(projectPath, `${agentId}.jsonl`);
|
|
425
|
+
const agentBackupPath = path4.join(agentBackupDir, `${agentId}.jsonl`);
|
|
426
|
+
yield* Effect3.tryPromise(() => fs3.rename(agentPath, agentBackupPath).catch(() => {
|
|
427
|
+
}));
|
|
428
|
+
}
|
|
429
|
+
const todosResult = yield* deleteLinkedTodos(sessionId, linkedAgents);
|
|
430
|
+
const backupPath = path4.join(backupDir, `${projectName}_${sessionId}.jsonl`);
|
|
431
|
+
yield* Effect3.tryPromise(() => fs3.rename(filePath, backupPath));
|
|
432
|
+
return {
|
|
433
|
+
success: true,
|
|
434
|
+
backupPath,
|
|
435
|
+
deletedAgents: linkedAgents.length,
|
|
436
|
+
deletedTodos: todosResult.deletedCount
|
|
437
|
+
};
|
|
438
|
+
});
|
|
439
|
+
var renameSession = (projectName, sessionId, newTitle) => Effect3.gen(function* () {
|
|
440
|
+
const filePath = path4.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
|
|
441
|
+
const content = yield* Effect3.tryPromise(() => fs3.readFile(filePath, "utf-8"));
|
|
442
|
+
const lines = content.trim().split("\n").filter(Boolean);
|
|
443
|
+
if (lines.length === 0) {
|
|
444
|
+
return { success: false, error: "Empty session" };
|
|
445
|
+
}
|
|
446
|
+
const messages = lines.map((line) => JSON.parse(line));
|
|
447
|
+
const firstUserIdx = messages.findIndex((m) => m.type === "user");
|
|
448
|
+
if (firstUserIdx === -1) {
|
|
449
|
+
return { success: false, error: "No user message found" };
|
|
450
|
+
}
|
|
451
|
+
const firstMsg = messages[firstUserIdx];
|
|
452
|
+
if (firstMsg?.message?.content && Array.isArray(firstMsg.message.content)) {
|
|
453
|
+
const textIdx = firstMsg.message.content.findIndex(
|
|
454
|
+
(item) => typeof item === "object" && item?.type === "text" && !item.text?.trim().startsWith("<ide_")
|
|
455
|
+
);
|
|
456
|
+
if (textIdx >= 0) {
|
|
457
|
+
const item = firstMsg.message.content[textIdx];
|
|
458
|
+
const oldText = item.text ?? "";
|
|
459
|
+
const cleanedText = oldText.replace(/^[^\n]+\n\n/, "");
|
|
460
|
+
item.text = `${newTitle}
|
|
461
|
+
|
|
462
|
+
${cleanedText}`;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
const newContent = messages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
466
|
+
yield* Effect3.tryPromise(() => fs3.writeFile(filePath, newContent, "utf-8"));
|
|
467
|
+
return { success: true };
|
|
468
|
+
});
|
|
469
|
+
var getSessionFiles = (projectName, sessionId) => Effect3.gen(function* () {
|
|
470
|
+
const messages = yield* readSession(projectName, sessionId);
|
|
471
|
+
const fileChanges = [];
|
|
472
|
+
const seenFiles = /* @__PURE__ */ new Set();
|
|
473
|
+
for (const msg of messages) {
|
|
474
|
+
if (msg.type === "file-history-snapshot") {
|
|
475
|
+
const snapshot = msg;
|
|
476
|
+
const backups = snapshot.snapshot?.trackedFileBackups;
|
|
477
|
+
if (backups && typeof backups === "object") {
|
|
478
|
+
for (const filePath of Object.keys(backups)) {
|
|
479
|
+
if (!seenFiles.has(filePath)) {
|
|
480
|
+
seenFiles.add(filePath);
|
|
481
|
+
fileChanges.push({
|
|
482
|
+
path: filePath,
|
|
483
|
+
action: "modified",
|
|
484
|
+
timestamp: snapshot.snapshot?.timestamp,
|
|
485
|
+
messageUuid: snapshot.messageId ?? msg.uuid
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
if (msg.type === "assistant" && msg.message?.content) {
|
|
492
|
+
const content = msg.message.content;
|
|
493
|
+
if (Array.isArray(content)) {
|
|
494
|
+
for (const item of content) {
|
|
495
|
+
if (item && typeof item === "object" && "type" in item && item.type === "tool_use") {
|
|
496
|
+
const toolUse = item;
|
|
497
|
+
if ((toolUse.name === "Write" || toolUse.name === "Edit") && toolUse.input?.file_path) {
|
|
498
|
+
const filePath = toolUse.input.file_path;
|
|
499
|
+
if (!seenFiles.has(filePath)) {
|
|
500
|
+
seenFiles.add(filePath);
|
|
501
|
+
fileChanges.push({
|
|
502
|
+
path: filePath,
|
|
503
|
+
action: toolUse.name === "Write" ? "created" : "modified",
|
|
504
|
+
timestamp: msg.timestamp,
|
|
505
|
+
messageUuid: msg.uuid
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
return {
|
|
515
|
+
sessionId,
|
|
516
|
+
projectName,
|
|
517
|
+
files: fileChanges,
|
|
518
|
+
totalChanges: fileChanges.length
|
|
519
|
+
};
|
|
520
|
+
});
|
|
521
|
+
var moveSession = (sourceProject, sessionId, targetProject) => Effect3.gen(function* () {
|
|
522
|
+
const sessionsDir = getSessionsDir();
|
|
523
|
+
const sourcePath = path4.join(sessionsDir, sourceProject);
|
|
524
|
+
const targetPath = path4.join(sessionsDir, targetProject);
|
|
525
|
+
const sourceFile = path4.join(sourcePath, `${sessionId}.jsonl`);
|
|
526
|
+
const targetFile = path4.join(targetPath, `${sessionId}.jsonl`);
|
|
527
|
+
const sourceExists = yield* Effect3.tryPromise(
|
|
528
|
+
() => fs3.access(sourceFile).then(() => true).catch(() => false)
|
|
529
|
+
);
|
|
530
|
+
if (!sourceExists) {
|
|
531
|
+
return { success: false, error: "Source session not found" };
|
|
532
|
+
}
|
|
533
|
+
const targetExists = yield* Effect3.tryPromise(
|
|
534
|
+
() => fs3.access(targetFile).then(() => true).catch(() => false)
|
|
535
|
+
);
|
|
536
|
+
if (targetExists) {
|
|
537
|
+
return { success: false, error: "Session already exists in target project" };
|
|
538
|
+
}
|
|
539
|
+
yield* Effect3.tryPromise(() => fs3.mkdir(targetPath, { recursive: true }));
|
|
540
|
+
const linkedAgents = yield* findLinkedAgents(sourceProject, sessionId);
|
|
541
|
+
yield* Effect3.tryPromise(() => fs3.rename(sourceFile, targetFile));
|
|
542
|
+
for (const agentId of linkedAgents) {
|
|
543
|
+
const sourceAgentFile = path4.join(sourcePath, `${agentId}.jsonl`);
|
|
544
|
+
const targetAgentFile = path4.join(targetPath, `${agentId}.jsonl`);
|
|
545
|
+
const agentExists = yield* Effect3.tryPromise(
|
|
546
|
+
() => fs3.access(sourceAgentFile).then(() => true).catch(() => false)
|
|
547
|
+
);
|
|
548
|
+
if (agentExists) {
|
|
549
|
+
yield* Effect3.tryPromise(() => fs3.rename(sourceAgentFile, targetAgentFile));
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
return { success: true };
|
|
553
|
+
});
|
|
554
|
+
var splitSession = (projectName, sessionId, splitAtMessageUuid) => Effect3.gen(function* () {
|
|
555
|
+
const projectPath = path4.join(getSessionsDir(), projectName);
|
|
556
|
+
const filePath = path4.join(projectPath, `${sessionId}.jsonl`);
|
|
557
|
+
const content = yield* Effect3.tryPromise(() => fs3.readFile(filePath, "utf-8"));
|
|
558
|
+
const lines = content.trim().split("\n").filter(Boolean);
|
|
559
|
+
const allMessages = lines.map((line) => JSON.parse(line));
|
|
560
|
+
const splitIndex = allMessages.findIndex((m) => m.uuid === splitAtMessageUuid);
|
|
561
|
+
if (splitIndex === -1) {
|
|
562
|
+
return { success: false, error: "Message not found" };
|
|
563
|
+
}
|
|
564
|
+
if (splitIndex === 0) {
|
|
565
|
+
return { success: false, error: "Cannot split at first message" };
|
|
566
|
+
}
|
|
567
|
+
const newSessionId = crypto.randomUUID();
|
|
568
|
+
const splitMessage = allMessages[splitIndex];
|
|
569
|
+
const shouldDuplicate = isContinuationSummary(splitMessage);
|
|
570
|
+
let remainingMessages;
|
|
571
|
+
const movedMessages = allMessages.slice(splitIndex);
|
|
572
|
+
if (shouldDuplicate) {
|
|
573
|
+
const duplicatedMessage = {
|
|
574
|
+
...splitMessage,
|
|
575
|
+
uuid: crypto.randomUUID(),
|
|
576
|
+
sessionId
|
|
577
|
+
// Keep original session ID
|
|
578
|
+
};
|
|
579
|
+
remainingMessages = [...allMessages.slice(0, splitIndex), duplicatedMessage];
|
|
580
|
+
} else {
|
|
581
|
+
remainingMessages = allMessages.slice(0, splitIndex);
|
|
582
|
+
}
|
|
583
|
+
const updatedMovedMessages = movedMessages.map((msg, index) => {
|
|
584
|
+
const updated = { ...msg, sessionId: newSessionId };
|
|
585
|
+
if (index === 0) {
|
|
586
|
+
updated.parentUuid = null;
|
|
587
|
+
}
|
|
588
|
+
return updated;
|
|
589
|
+
});
|
|
590
|
+
const remainingContent = remainingMessages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
591
|
+
yield* Effect3.tryPromise(() => fs3.writeFile(filePath, remainingContent, "utf-8"));
|
|
592
|
+
const newFilePath = path4.join(projectPath, `${newSessionId}.jsonl`);
|
|
593
|
+
const newContent = updatedMovedMessages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
594
|
+
yield* Effect3.tryPromise(() => fs3.writeFile(newFilePath, newContent, "utf-8"));
|
|
595
|
+
const agentFiles = yield* Effect3.tryPromise(() => fs3.readdir(projectPath));
|
|
596
|
+
const agentJsonlFiles = agentFiles.filter((f) => f.startsWith("agent-") && f.endsWith(".jsonl"));
|
|
597
|
+
for (const agentFile of agentJsonlFiles) {
|
|
598
|
+
const agentPath = path4.join(projectPath, agentFile);
|
|
599
|
+
const agentContent = yield* Effect3.tryPromise(() => fs3.readFile(agentPath, "utf-8"));
|
|
600
|
+
const agentLines = agentContent.trim().split("\n").filter(Boolean);
|
|
601
|
+
if (agentLines.length === 0) continue;
|
|
602
|
+
const firstAgentMsg = JSON.parse(agentLines[0]);
|
|
603
|
+
if (firstAgentMsg.sessionId === sessionId) {
|
|
604
|
+
const agentId = agentFile.replace("agent-", "").replace(".jsonl", "");
|
|
605
|
+
const isRelatedToMoved = movedMessages.some(
|
|
606
|
+
(msg) => msg.agentId === agentId
|
|
607
|
+
);
|
|
608
|
+
if (isRelatedToMoved) {
|
|
609
|
+
const updatedAgentMessages = agentLines.map((line) => {
|
|
610
|
+
const msg = JSON.parse(line);
|
|
611
|
+
return JSON.stringify({ ...msg, sessionId: newSessionId });
|
|
612
|
+
});
|
|
613
|
+
const updatedAgentContent = updatedAgentMessages.join("\n") + "\n";
|
|
614
|
+
yield* Effect3.tryPromise(() => fs3.writeFile(agentPath, updatedAgentContent, "utf-8"));
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
return {
|
|
619
|
+
success: true,
|
|
620
|
+
newSessionId,
|
|
621
|
+
newSessionPath: newFilePath,
|
|
622
|
+
movedMessageCount: movedMessages.length,
|
|
623
|
+
duplicatedSummary: shouldDuplicate
|
|
624
|
+
};
|
|
625
|
+
});
|
|
626
|
+
var cleanInvalidMessages = (projectName, sessionId) => Effect3.gen(function* () {
|
|
627
|
+
const filePath = path4.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
|
|
628
|
+
const content = yield* Effect3.tryPromise(() => fs3.readFile(filePath, "utf-8"));
|
|
629
|
+
const lines = content.trim().split("\n").filter(Boolean);
|
|
630
|
+
if (lines.length === 0) return { removedCount: 0, remainingCount: 0 };
|
|
631
|
+
const messages = lines.map((line) => JSON.parse(line));
|
|
632
|
+
const invalidIndices = [];
|
|
633
|
+
messages.forEach((msg, idx) => {
|
|
634
|
+
if (isInvalidApiKeyMessage(msg)) {
|
|
635
|
+
invalidIndices.push(idx);
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
if (invalidIndices.length === 0) {
|
|
639
|
+
const userAssistantCount = messages.filter(
|
|
640
|
+
(m) => m.type === "user" || m.type === "assistant"
|
|
641
|
+
).length;
|
|
642
|
+
const hasSummary2 = messages.some((m) => m.type === "summary");
|
|
643
|
+
const remainingCount2 = userAssistantCount > 0 ? userAssistantCount : hasSummary2 ? 1 : 0;
|
|
644
|
+
return { removedCount: 0, remainingCount: remainingCount2 };
|
|
645
|
+
}
|
|
646
|
+
const filtered = [];
|
|
647
|
+
let lastValidUuid = null;
|
|
648
|
+
for (let i = 0; i < messages.length; i++) {
|
|
649
|
+
if (invalidIndices.includes(i)) {
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
const msg = messages[i];
|
|
653
|
+
if (msg.parentUuid && invalidIndices.some((idx) => messages[idx]?.uuid === msg.parentUuid)) {
|
|
654
|
+
msg.parentUuid = lastValidUuid;
|
|
655
|
+
}
|
|
656
|
+
filtered.push(msg);
|
|
657
|
+
lastValidUuid = msg.uuid;
|
|
658
|
+
}
|
|
659
|
+
const newContent = filtered.length > 0 ? filtered.map((m) => JSON.stringify(m)).join("\n") + "\n" : "";
|
|
660
|
+
yield* Effect3.tryPromise(() => fs3.writeFile(filePath, newContent, "utf-8"));
|
|
661
|
+
const remainingUserAssistant = filtered.filter(
|
|
662
|
+
(m) => m.type === "user" || m.type === "assistant"
|
|
663
|
+
).length;
|
|
664
|
+
const hasSummary = filtered.some((m) => m.type === "summary");
|
|
665
|
+
const remainingCount = remainingUserAssistant > 0 ? remainingUserAssistant : hasSummary ? 1 : 0;
|
|
666
|
+
return { removedCount: invalidIndices.length, remainingCount };
|
|
667
|
+
});
|
|
668
|
+
var previewCleanup = (projectName) => Effect3.gen(function* () {
|
|
669
|
+
const projects = yield* listProjects;
|
|
670
|
+
const targetProjects = projectName ? projects.filter((p) => p.name === projectName) : projects;
|
|
671
|
+
const orphanTodos = yield* findOrphanTodos();
|
|
672
|
+
const orphanTodoCount = orphanTodos.length;
|
|
673
|
+
const results = yield* Effect3.all(
|
|
674
|
+
targetProjects.map(
|
|
675
|
+
(project) => Effect3.gen(function* () {
|
|
676
|
+
const sessions = yield* listSessions(project.name);
|
|
677
|
+
const emptySessions = sessions.filter((s) => s.messageCount === 0);
|
|
678
|
+
const invalidSessions = sessions.filter(
|
|
679
|
+
(s) => s.title?.includes("Invalid API key") || s.title?.includes("API key")
|
|
680
|
+
);
|
|
681
|
+
let emptyWithTodosCount = 0;
|
|
682
|
+
for (const session of emptySessions) {
|
|
683
|
+
const linkedAgents = yield* findLinkedAgents(project.name, session.id);
|
|
684
|
+
const hasTodos = yield* sessionHasTodos(session.id, linkedAgents);
|
|
685
|
+
if (hasTodos) {
|
|
686
|
+
emptyWithTodosCount++;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
const orphanAgents = yield* findOrphanAgents(project.name);
|
|
690
|
+
return {
|
|
691
|
+
project: project.name,
|
|
692
|
+
emptySessions,
|
|
693
|
+
invalidSessions,
|
|
694
|
+
emptyWithTodosCount,
|
|
695
|
+
orphanAgentCount: orphanAgents.length,
|
|
696
|
+
orphanTodoCount: 0
|
|
697
|
+
// Will set for first project only
|
|
698
|
+
};
|
|
699
|
+
})
|
|
700
|
+
),
|
|
701
|
+
{ concurrency: 5 }
|
|
702
|
+
);
|
|
703
|
+
if (results.length > 0) {
|
|
704
|
+
results[0] = { ...results[0], orphanTodoCount };
|
|
705
|
+
}
|
|
706
|
+
return results;
|
|
707
|
+
});
|
|
708
|
+
var clearSessions = (options) => Effect3.gen(function* () {
|
|
709
|
+
const {
|
|
710
|
+
projectName,
|
|
711
|
+
clearEmpty = true,
|
|
712
|
+
clearInvalid = true,
|
|
713
|
+
skipWithTodos = true,
|
|
714
|
+
clearOrphanAgents = false,
|
|
715
|
+
clearOrphanTodos = false
|
|
716
|
+
} = options;
|
|
717
|
+
const projects = yield* listProjects;
|
|
718
|
+
const targetProjects = projectName ? projects.filter((p) => p.name === projectName) : projects;
|
|
719
|
+
let deletedSessionCount = 0;
|
|
720
|
+
let removedMessageCount = 0;
|
|
721
|
+
let deletedOrphanAgentCount = 0;
|
|
722
|
+
let deletedOrphanTodoCount = 0;
|
|
723
|
+
const sessionsToDelete = [];
|
|
724
|
+
if (clearInvalid) {
|
|
725
|
+
for (const project of targetProjects) {
|
|
726
|
+
const projectPath = path4.join(getSessionsDir(), project.name);
|
|
727
|
+
const files = yield* Effect3.tryPromise(() => fs3.readdir(projectPath));
|
|
728
|
+
const sessionFiles = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
729
|
+
for (const file of sessionFiles) {
|
|
730
|
+
const sessionId = file.replace(".jsonl", "");
|
|
731
|
+
const result = yield* cleanInvalidMessages(project.name, sessionId);
|
|
732
|
+
removedMessageCount += result.removedCount;
|
|
733
|
+
if (result.remainingCount === 0) {
|
|
734
|
+
sessionsToDelete.push({ project: project.name, sessionId });
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
if (clearEmpty) {
|
|
740
|
+
for (const project of targetProjects) {
|
|
741
|
+
const sessions = yield* listSessions(project.name);
|
|
742
|
+
for (const session of sessions) {
|
|
743
|
+
if (session.messageCount === 0) {
|
|
744
|
+
const alreadyMarked = sessionsToDelete.some(
|
|
745
|
+
(s) => s.project === project.name && s.sessionId === session.id
|
|
746
|
+
);
|
|
747
|
+
if (!alreadyMarked) {
|
|
748
|
+
if (skipWithTodos) {
|
|
749
|
+
const linkedAgents = yield* findLinkedAgents(project.name, session.id);
|
|
750
|
+
const hasTodos = yield* sessionHasTodos(session.id, linkedAgents);
|
|
751
|
+
if (hasTodos) continue;
|
|
752
|
+
}
|
|
753
|
+
sessionsToDelete.push({ project: project.name, sessionId: session.id });
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
for (const { project, sessionId } of sessionsToDelete) {
|
|
760
|
+
yield* deleteSession(project, sessionId);
|
|
761
|
+
deletedSessionCount++;
|
|
762
|
+
}
|
|
763
|
+
if (clearOrphanAgents) {
|
|
764
|
+
for (const project of targetProjects) {
|
|
765
|
+
const result = yield* deleteOrphanAgents(project.name);
|
|
766
|
+
deletedOrphanAgentCount += result.count;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
if (clearOrphanTodos) {
|
|
770
|
+
const result = yield* deleteOrphanTodos();
|
|
771
|
+
deletedOrphanTodoCount = result.deletedCount;
|
|
772
|
+
}
|
|
773
|
+
return {
|
|
774
|
+
success: true,
|
|
775
|
+
deletedCount: deletedSessionCount,
|
|
776
|
+
removedMessageCount,
|
|
777
|
+
deletedOrphanAgentCount,
|
|
778
|
+
deletedOrphanTodoCount
|
|
779
|
+
};
|
|
780
|
+
});
|
|
781
|
+
export {
|
|
782
|
+
clearSessions,
|
|
783
|
+
deleteLinkedTodos,
|
|
784
|
+
deleteMessage,
|
|
785
|
+
deleteOrphanAgents,
|
|
786
|
+
deleteOrphanTodos,
|
|
787
|
+
deleteSession,
|
|
788
|
+
displayPathToFolderName,
|
|
789
|
+
extractTextContent,
|
|
790
|
+
extractTitle,
|
|
791
|
+
findLinkedAgents,
|
|
792
|
+
findLinkedTodos,
|
|
793
|
+
findOrphanAgents,
|
|
794
|
+
findOrphanTodos,
|
|
795
|
+
folderNameToDisplayPath,
|
|
796
|
+
getSessionFiles,
|
|
797
|
+
getSessionsDir,
|
|
798
|
+
getTodosDir,
|
|
799
|
+
isContinuationSummary,
|
|
800
|
+
isInvalidApiKeyMessage,
|
|
801
|
+
listProjects,
|
|
802
|
+
listSessions,
|
|
803
|
+
moveSession,
|
|
804
|
+
previewCleanup,
|
|
805
|
+
readSession,
|
|
806
|
+
renameSession,
|
|
807
|
+
sessionHasTodos,
|
|
808
|
+
splitSession
|
|
809
|
+
};
|
|
810
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/paths.ts","../src/utils.ts","../src/agents.ts","../src/todos.ts","../src/session.ts"],"sourcesContent":["/**\n * Path utilities for Claude Code session management\n */\nimport * as path from 'node:path'\nimport * as os from 'node:os'\n\n// Get Claude sessions directory (~/.claude/projects)\nexport const getSessionsDir = (): string => path.join(os.homedir(), '.claude', 'projects')\n\n// Get Claude todos directory (~/.claude/todos)\nexport const getTodosDir = (): string => path.join(os.homedir(), '.claude', 'todos')\n\n// Convert project folder name to display path\n// e.g., -Users-david-works -> /Users/david/works\n// Handle dot-prefixed folders: --claude -> /.claude, -works--vscode -> /works/.vscode\nexport const folderNameToDisplayPath = (folderName: string): string => {\n return folderName\n .replace(/^-/, '/')\n .replace(/--/g, '/.') // double dash means dot-prefixed folder\n .replace(/-/g, '/')\n}\n\n// Convert display path to folder name (reverse of above)\nexport const displayPathToFolderName = (displayPath: string): string => {\n return displayPath\n .replace(/^\\//g, '-')\n .replace(/\\/\\./g, '--') // dot-prefixed folder becomes double dash\n .replace(/\\//g, '-')\n}\n","/**\n * Utility functions for message processing\n */\nimport type { ContentItem, Message, MessagePayload } from './types.js'\n\n// Extract text content from message payload\nexport const extractTextContent = (message: MessagePayload | undefined): string => {\n if (!message) return ''\n\n const content = message.content\n if (!content) return ''\n\n // If content is string, return directly\n if (typeof content === 'string') return content\n\n // If content is array, extract text items\n if (Array.isArray(content)) {\n return content\n .filter((item): item is ContentItem => typeof item === 'object' && item?.type === 'text')\n .map((item) => item.text ?? '')\n .join('')\n }\n\n return ''\n}\n\n// Extract title from text content (remove IDE tags, use first line)\nexport const extractTitle = (text: string): string => {\n if (!text) return 'Untitled'\n\n // Remove IDE tags (<ide_opened_file>, <ide_selection>, etc.)\n let cleaned = text.replace(/<ide_[^>]*>[\\s\\S]*?<\\/ide_[^>]*>/g, '').trim()\n\n if (!cleaned) return 'Untitled'\n\n // Use only content before \\n\\n or \\n as title\n if (cleaned.includes('\\n\\n')) {\n cleaned = cleaned.split('\\n\\n')[0]\n } else if (cleaned.includes('\\n')) {\n cleaned = cleaned.split('\\n')[0]\n }\n\n // Limit to 100 characters\n if (cleaned.length > 100) {\n return cleaned.slice(0, 100) + '...'\n }\n\n return cleaned || 'Untitled'\n}\n\n// Check if message contains \"Invalid API key\"\nexport const isInvalidApiKeyMessage = (msg: Message): boolean => {\n const text = extractTextContent(msg.message)\n return text.includes('Invalid API key')\n}\n\n// Check if a message is a continuation summary (from compact)\nexport const isContinuationSummary = (msg: Record<string, unknown>): boolean => {\n // isCompactSummary flag is set by Claude Code for continuation summaries\n if (msg.isCompactSummary === true) return true\n\n // Fallback: check message content\n if (msg.type !== 'user') return false\n const message = msg.message as { content?: string } | undefined\n const content = message?.content ?? ''\n return content.startsWith('This session is being continued from')\n}\n","/**\n * Agent file management utilities\n */\nimport { Effect } from 'effect'\nimport * as fs from 'node:fs/promises'\nimport * as path from 'node:path'\nimport { getSessionsDir } from './paths.js'\n\n// Find agent files linked to a session\nexport const findLinkedAgents = (projectName: string, sessionId: string) =>\n Effect.gen(function* () {\n const projectPath = path.join(getSessionsDir(), projectName)\n const files = yield* Effect.tryPromise(() => fs.readdir(projectPath))\n const agentFiles = files.filter((f) => f.startsWith('agent-') && f.endsWith('.jsonl'))\n\n const linkedAgents: string[] = []\n\n for (const agentFile of agentFiles) {\n const filePath = path.join(projectPath, agentFile)\n const content = yield* Effect.tryPromise(() => fs.readFile(filePath, 'utf-8'))\n const firstLine = content.split('\\n')[0]\n\n if (firstLine) {\n try {\n const parsed = JSON.parse(firstLine) as { sessionId?: string }\n if (parsed.sessionId === sessionId) {\n linkedAgents.push(agentFile.replace('.jsonl', ''))\n }\n } catch {\n // Skip invalid JSON\n }\n }\n }\n\n return linkedAgents\n })\n\n// Find orphan agent files (agents whose parent session no longer exists)\nexport const findOrphanAgents = (projectName: string) =>\n Effect.gen(function* () {\n const projectPath = path.join(getSessionsDir(), projectName)\n const files = yield* Effect.tryPromise(() => fs.readdir(projectPath))\n\n const sessionIds = new Set(\n files\n .filter((f) => !f.startsWith('agent-') && f.endsWith('.jsonl'))\n .map((f) => f.replace('.jsonl', ''))\n )\n\n const agentFiles = files.filter((f) => f.startsWith('agent-') && f.endsWith('.jsonl'))\n const orphanAgents: Array<{ agentId: string; sessionId: string }> = []\n\n for (const agentFile of agentFiles) {\n const filePath = path.join(projectPath, agentFile)\n const content = yield* Effect.tryPromise(() => fs.readFile(filePath, 'utf-8'))\n const firstLine = content.split('\\n')[0]\n\n if (firstLine) {\n try {\n const parsed = JSON.parse(firstLine) as { sessionId?: string }\n if (parsed.sessionId && !sessionIds.has(parsed.sessionId)) {\n orphanAgents.push({\n agentId: agentFile.replace('.jsonl', ''),\n sessionId: parsed.sessionId,\n })\n }\n } catch {\n // Skip invalid JSON\n }\n }\n }\n\n return orphanAgents\n })\n\n// Delete orphan agent files (move to .bak)\nexport const deleteOrphanAgents = (projectName: string) =>\n Effect.gen(function* () {\n const projectPath = path.join(getSessionsDir(), projectName)\n const orphans = yield* findOrphanAgents(projectName)\n\n // Create backup directory\n const backupDir = path.join(projectPath, '.bak')\n yield* Effect.tryPromise(() => fs.mkdir(backupDir, { recursive: true }))\n\n const deletedAgents: string[] = []\n\n for (const orphan of orphans) {\n const agentPath = path.join(projectPath, `${orphan.agentId}.jsonl`)\n const agentBackupPath = path.join(backupDir, `${orphan.agentId}.jsonl`)\n yield* Effect.tryPromise(() => fs.rename(agentPath, agentBackupPath))\n deletedAgents.push(orphan.agentId)\n }\n\n return { success: true, deletedAgents, count: deletedAgents.length }\n })\n","/**\n * Todo file management utilities\n */\nimport { Effect } from 'effect'\nimport * as fs from 'node:fs/promises'\nimport * as path from 'node:path'\nimport { getSessionsDir, getTodosDir } from './paths.js'\nimport type { TodoItem, SessionTodos } from './types.js'\n\n// Find linked todo files for a session and its agents\nexport const findLinkedTodos = (sessionId: string, agentIds: string[]) =>\n Effect.gen(function* () {\n const todosDir = getTodosDir()\n\n // Check if todos directory exists\n const exists = yield* Effect.tryPromise(() =>\n fs\n .access(todosDir)\n .then(() => true)\n .catch(() => false)\n )\n\n if (!exists) {\n return {\n sessionId,\n sessionTodos: [],\n agentTodos: [],\n hasTodos: false,\n } satisfies SessionTodos\n }\n\n // Read session's own todo file\n const sessionTodoPath = path.join(todosDir, `${sessionId}.json`)\n let sessionTodos: TodoItem[] = []\n\n const sessionTodoExists = yield* Effect.tryPromise(() =>\n fs\n .access(sessionTodoPath)\n .then(() => true)\n .catch(() => false)\n )\n\n if (sessionTodoExists) {\n const content = yield* Effect.tryPromise(() => fs.readFile(sessionTodoPath, 'utf-8'))\n try {\n sessionTodos = JSON.parse(content) as TodoItem[]\n } catch {\n // Invalid JSON, treat as empty\n }\n }\n\n // Read agent todo files\n const agentTodos: { agentId: string; todos: TodoItem[] }[] = []\n\n for (const agentId of agentIds) {\n // Agent todo files are named: {sessionId}-{agentId}.json\n const shortAgentId = agentId.replace('agent-', '')\n const agentTodoPath = path.join(todosDir, `${sessionId}-agent-${shortAgentId}.json`)\n\n const agentTodoExists = yield* Effect.tryPromise(() =>\n fs\n .access(agentTodoPath)\n .then(() => true)\n .catch(() => false)\n )\n\n if (agentTodoExists) {\n const content = yield* Effect.tryPromise(() => fs.readFile(agentTodoPath, 'utf-8'))\n try {\n const todos = JSON.parse(content) as TodoItem[]\n agentTodos.push({ agentId, todos })\n } catch {\n // Invalid JSON, skip\n }\n }\n }\n\n const hasTodos = sessionTodos.length > 0 || agentTodos.some((at) => at.todos.length > 0)\n\n return {\n sessionId,\n sessionTodos,\n agentTodos,\n hasTodos,\n } satisfies SessionTodos\n })\n\n// Check if session has any todos (quick check)\nexport const sessionHasTodos = (sessionId: string, agentIds: string[]) =>\n Effect.gen(function* () {\n const todosDir = getTodosDir()\n\n // Check if todos directory exists\n const exists = yield* Effect.tryPromise(() =>\n fs\n .access(todosDir)\n .then(() => true)\n .catch(() => false)\n )\n\n if (!exists) return false\n\n // Check session's own todo file\n const sessionTodoPath = path.join(todosDir, `${sessionId}.json`)\n const sessionTodoExists = yield* Effect.tryPromise(() =>\n fs\n .access(sessionTodoPath)\n .then(() => true)\n .catch(() => false)\n )\n\n if (sessionTodoExists) {\n const content = yield* Effect.tryPromise(() => fs.readFile(sessionTodoPath, 'utf-8'))\n try {\n const todos = JSON.parse(content) as TodoItem[]\n if (todos.length > 0) return true\n } catch {\n // Invalid JSON, continue\n }\n }\n\n // Check agent todo files\n for (const agentId of agentIds) {\n const shortAgentId = agentId.replace('agent-', '')\n const agentTodoPath = path.join(todosDir, `${sessionId}-agent-${shortAgentId}.json`)\n\n const agentTodoExists = yield* Effect.tryPromise(() =>\n fs\n .access(agentTodoPath)\n .then(() => true)\n .catch(() => false)\n )\n\n if (agentTodoExists) {\n const content = yield* Effect.tryPromise(() => fs.readFile(agentTodoPath, 'utf-8'))\n try {\n const todos = JSON.parse(content) as TodoItem[]\n if (todos.length > 0) return true\n } catch {\n // Invalid JSON, continue\n }\n }\n }\n\n return false\n })\n\n// Delete linked todo files for a session (move to .bak)\nexport const deleteLinkedTodos = (sessionId: string, agentIds: string[]) =>\n Effect.gen(function* () {\n const todosDir = getTodosDir()\n\n // Check if todos directory exists\n const exists = yield* Effect.tryPromise(() =>\n fs\n .access(todosDir)\n .then(() => true)\n .catch(() => false)\n )\n\n if (!exists) return { deletedCount: 0 }\n\n // Create backup directory\n const backupDir = path.join(todosDir, '.bak')\n yield* Effect.tryPromise(() => fs.mkdir(backupDir, { recursive: true }))\n\n let deletedCount = 0\n\n // Delete session's own todo file\n const sessionTodoPath = path.join(todosDir, `${sessionId}.json`)\n const sessionTodoExists = yield* Effect.tryPromise(() =>\n fs\n .access(sessionTodoPath)\n .then(() => true)\n .catch(() => false)\n )\n\n if (sessionTodoExists) {\n const backupPath = path.join(backupDir, `${sessionId}.json`)\n yield* Effect.tryPromise(() => fs.rename(sessionTodoPath, backupPath))\n deletedCount++\n }\n\n // Delete agent todo files\n for (const agentId of agentIds) {\n const shortAgentId = agentId.replace('agent-', '')\n const agentTodoPath = path.join(todosDir, `${sessionId}-agent-${shortAgentId}.json`)\n\n const agentTodoExists = yield* Effect.tryPromise(() =>\n fs\n .access(agentTodoPath)\n .then(() => true)\n .catch(() => false)\n )\n\n if (agentTodoExists) {\n const backupPath = path.join(backupDir, `${sessionId}-agent-${shortAgentId}.json`)\n yield* Effect.tryPromise(() => fs.rename(agentTodoPath, backupPath))\n deletedCount++\n }\n }\n\n return { deletedCount }\n })\n\n// Find all orphan todo files (session no longer exists)\nexport const findOrphanTodos = () =>\n Effect.gen(function* () {\n const todosDir = getTodosDir()\n const sessionsDir = getSessionsDir()\n\n // Check if directories exist\n const [todosExists, sessionsExists] = yield* Effect.all([\n Effect.tryPromise(() =>\n fs\n .access(todosDir)\n .then(() => true)\n .catch(() => false)\n ),\n Effect.tryPromise(() =>\n fs\n .access(sessionsDir)\n .then(() => true)\n .catch(() => false)\n ),\n ])\n\n if (!todosExists || !sessionsExists) return []\n\n // Get all todo files\n const todoFiles = yield* Effect.tryPromise(() => fs.readdir(todosDir))\n const jsonFiles = todoFiles.filter((f) => f.endsWith('.json'))\n\n // Build set of all valid session IDs across all projects\n const validSessionIds = new Set<string>()\n const projectEntries = yield* Effect.tryPromise(() =>\n fs.readdir(sessionsDir, { withFileTypes: true })\n )\n\n for (const entry of projectEntries) {\n if (!entry.isDirectory() || entry.name.startsWith('.')) continue\n const projectPath = path.join(sessionsDir, entry.name)\n const files = yield* Effect.tryPromise(() => fs.readdir(projectPath))\n for (const f of files) {\n if (f.endsWith('.jsonl') && !f.startsWith('agent-')) {\n validSessionIds.add(f.replace('.jsonl', ''))\n }\n }\n }\n\n // Find orphan todo files\n const orphans: string[] = []\n for (const todoFile of jsonFiles) {\n // Parse session ID from todo filename\n // Format: {sessionId}.json or {sessionId}-agent-{agentId}.json\n const match = todoFile.match(/^([a-f0-9-]+)(?:-agent-[a-f0-9]+)?\\.json$/)\n if (match) {\n const sessionId = match[1]\n if (!validSessionIds.has(sessionId)) {\n orphans.push(todoFile)\n }\n }\n }\n\n return orphans\n })\n\n// Delete orphan todo files\nexport const deleteOrphanTodos = () =>\n Effect.gen(function* () {\n const todosDir = getTodosDir()\n const orphans = yield* findOrphanTodos()\n\n if (orphans.length === 0) return { success: true, deletedCount: 0 }\n\n // Create backup directory\n const backupDir = path.join(todosDir, '.bak')\n yield* Effect.tryPromise(() => fs.mkdir(backupDir, { recursive: true }))\n\n let deletedCount = 0\n\n for (const orphan of orphans) {\n const filePath = path.join(todosDir, orphan)\n const backupPath = path.join(backupDir, orphan)\n yield* Effect.tryPromise(() => fs.rename(filePath, backupPath))\n deletedCount++\n }\n\n return { success: true, deletedCount }\n })\n","/**\n * Session management operations\n */\nimport { Effect, pipe, Array as A, Option as O } from 'effect'\nimport * as fs from 'node:fs/promises'\nimport * as path from 'node:path'\nimport { getSessionsDir, folderNameToDisplayPath } from './paths.js'\nimport { extractTextContent, extractTitle, isInvalidApiKeyMessage, isContinuationSummary } from './utils.js'\nimport { findLinkedAgents, findOrphanAgents, deleteOrphanAgents } from './agents.js'\nimport { deleteLinkedTodos, sessionHasTodos, findOrphanTodos, deleteOrphanTodos } from './todos.js'\nimport type {\n Message,\n SessionMeta,\n Project,\n FileChange,\n SessionFilesSummary,\n DeleteSessionResult,\n RenameSessionResult,\n SplitSessionResult,\n MoveSessionResult,\n ClearSessionsResult,\n CleanupPreview,\n ContentItem,\n} from './types.js'\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() && !e.name.startsWith('.'))\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 // Exclude agent- files (subagent logs)\n const sessionFiles = files.filter((f) => f.endsWith('.jsonl') && !f.startsWith('agent-'))\n\n return {\n name: entry.name,\n displayName: folderNameToDisplayPath(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 // Exclude agent- files (subagent logs)\n const sessionFiles = files.filter((f) => f.endsWith('.jsonl') && !f.startsWith('agent-'))\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\n // Filter only user/assistant messages for counting\n const userAssistantMessages = messages.filter(\n (m) => m.type === 'user' || m.type === 'assistant'\n )\n\n // Check if session has summary (for preserved sessions without user/assistant messages)\n const hasSummary = messages.some((m) => m.type === 'summary')\n\n const firstMessage = userAssistantMessages[0]\n const lastMessage = userAssistantMessages[userAssistantMessages.length - 1]\n\n // Extract title from first user message\n const title = pipe(\n messages,\n A.findFirst((m) => m.type === 'user'),\n O.map((m) => {\n const text = extractTextContent(m.message)\n return extractTitle(text)\n }),\n O.getOrElse(() => (hasSummary ? '[Summary Only]' : `Session ${sessionId.slice(0, 8)}`))\n )\n\n return {\n id: sessionId,\n projectName,\n title,\n // If session has summary but no user/assistant messages, count as 1\n messageCount:\n userAssistantMessages.length > 0 ? userAssistantMessages.length : hasSummary ? 1 : 0,\n createdAt: firstMessage?.timestamp,\n updatedAt: lastMessage?.timestamp,\n } satisfies SessionMeta\n })\n ),\n { concurrency: 10 }\n )\n\n // Sort by newest first\n return sessions.sort((a, b) => {\n const dateA = a.updatedAt ? new Date(a.updatedAt).getTime() : 0\n const dateB = b.updatedAt ? new Date(b.updatedAt).getTime() : 0\n return dateB - dateA\n })\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 message from session and repair parentUuid chain\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 Record<string, unknown>)\n\n // Find by uuid or messageId (for file-history-snapshot type)\n const targetIndex = messages.findIndex(\n (m) => m.uuid === messageUuid || m.messageId === messageUuid\n )\n if (targetIndex === -1) {\n return { success: false, error: 'Message not found' }\n }\n\n // Get the deleted message's uuid and parentUuid\n const deletedMsg = messages[targetIndex]\n const deletedUuid = deletedMsg?.uuid ?? deletedMsg?.messageId\n const parentUuid = deletedMsg?.parentUuid\n\n // Find all messages that reference the deleted message as their parent\n // and update them to point to the deleted message's parent\n for (const msg of messages) {\n if (msg.parentUuid === deletedUuid) {\n msg.parentUuid = parentUuid\n }\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// Delete a session and its linked agent/todo files\nexport const deleteSession = (projectName: string, sessionId: string) =>\n Effect.gen(function* () {\n const sessionsDir = getSessionsDir()\n const projectPath = path.join(sessionsDir, projectName)\n const filePath = path.join(projectPath, `${sessionId}.jsonl`)\n\n // Find linked agents first (before any deletion)\n const linkedAgents = yield* findLinkedAgents(projectName, sessionId)\n\n // Check file size - if empty (0 bytes), just delete without backup\n const stat = yield* Effect.tryPromise(() => fs.stat(filePath))\n if (stat.size === 0) {\n yield* Effect.tryPromise(() => fs.unlink(filePath))\n // Still delete linked agents and todos for empty sessions\n const agentBackupDir = path.join(projectPath, '.bak')\n yield* Effect.tryPromise(() => fs.mkdir(agentBackupDir, { recursive: true }))\n for (const agentId of linkedAgents) {\n const agentPath = path.join(projectPath, `${agentId}.jsonl`)\n const agentBackupPath = path.join(agentBackupDir, `${agentId}.jsonl`)\n yield* Effect.tryPromise(() => fs.rename(agentPath, agentBackupPath).catch(() => {}))\n }\n yield* deleteLinkedTodos(sessionId, linkedAgents)\n return { success: true, deletedAgents: linkedAgents.length } satisfies DeleteSessionResult\n }\n\n // Create backup directory\n const backupDir = path.join(sessionsDir, '.bak')\n yield* Effect.tryPromise(() => fs.mkdir(backupDir, { recursive: true }))\n\n // Delete linked agent files (move to .bak in project folder)\n const agentBackupDir = path.join(projectPath, '.bak')\n yield* Effect.tryPromise(() => fs.mkdir(agentBackupDir, { recursive: true }))\n for (const agentId of linkedAgents) {\n const agentPath = path.join(projectPath, `${agentId}.jsonl`)\n const agentBackupPath = path.join(agentBackupDir, `${agentId}.jsonl`)\n yield* Effect.tryPromise(() => fs.rename(agentPath, agentBackupPath).catch(() => {}))\n }\n\n // Delete linked todo files\n const todosResult = yield* deleteLinkedTodos(sessionId, linkedAgents)\n\n // Move session to backup (format: project_name_session_id.jsonl)\n const backupPath = path.join(backupDir, `${projectName}_${sessionId}.jsonl`)\n yield* Effect.tryPromise(() => fs.rename(filePath, backupPath))\n\n return {\n success: true,\n backupPath,\n deletedAgents: linkedAgents.length,\n deletedTodos: todosResult.deletedCount,\n } satisfies DeleteSessionResult\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' } satisfies RenameSessionResult\n }\n\n const messages = lines.map((line) => JSON.parse(line) as Message)\n\n // Find first user message\n const firstUserIdx = messages.findIndex((m) => m.type === 'user')\n if (firstUserIdx === -1) {\n return { success: false, error: 'No user message found' } satisfies RenameSessionResult\n }\n\n const firstMsg = messages[firstUserIdx]\n if (firstMsg?.message?.content && Array.isArray(firstMsg.message.content)) {\n // Find first non-IDE text content\n const textIdx = firstMsg.message.content.findIndex(\n (item): item is ContentItem =>\n typeof item === 'object' &&\n item?.type === 'text' &&\n !item.text?.trim().startsWith('<ide_')\n )\n\n if (textIdx >= 0) {\n const item = firstMsg.message.content[textIdx] as ContentItem\n const oldText = item.text ?? ''\n // Remove existing title pattern (first line ending with \\n\\n)\n const cleanedText = oldText.replace(/^[^\\n]+\\n\\n/, '')\n item.text = `${newTitle}\\n\\n${cleanedText}`\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 } satisfies RenameSessionResult\n })\n\n// Get files changed in a session (from file-history-snapshot and tool_use)\nexport const getSessionFiles = (projectName: string, sessionId: string) =>\n Effect.gen(function* () {\n const messages = yield* readSession(projectName, sessionId)\n const fileChanges: FileChange[] = []\n const seenFiles = new Set<string>()\n\n for (const msg of messages) {\n // Check file-history-snapshot type\n if (msg.type === 'file-history-snapshot') {\n const snapshot = msg as unknown as {\n type: string\n messageId?: string\n snapshot?: {\n trackedFileBackups?: Record<string, unknown>\n timestamp?: string\n }\n }\n const backups = snapshot.snapshot?.trackedFileBackups\n if (backups && typeof backups === 'object') {\n for (const filePath of Object.keys(backups)) {\n if (!seenFiles.has(filePath)) {\n seenFiles.add(filePath)\n fileChanges.push({\n path: filePath,\n action: 'modified',\n timestamp: snapshot.snapshot?.timestamp,\n messageUuid: snapshot.messageId ?? msg.uuid,\n })\n }\n }\n }\n }\n\n // Check tool_use for Write/Edit operations\n if (msg.type === 'assistant' && msg.message?.content) {\n const content = msg.message.content\n if (Array.isArray(content)) {\n for (const item of content) {\n if (item && typeof item === 'object' && 'type' in item && item.type === 'tool_use') {\n const toolUse = item as { name?: string; input?: { file_path?: string } }\n if (\n (toolUse.name === 'Write' || toolUse.name === 'Edit') &&\n toolUse.input?.file_path\n ) {\n const filePath = toolUse.input.file_path\n if (!seenFiles.has(filePath)) {\n seenFiles.add(filePath)\n fileChanges.push({\n path: filePath,\n action: toolUse.name === 'Write' ? 'created' : 'modified',\n timestamp: msg.timestamp,\n messageUuid: msg.uuid,\n })\n }\n }\n }\n }\n }\n }\n }\n\n return {\n sessionId,\n projectName,\n files: fileChanges,\n totalChanges: fileChanges.length,\n } satisfies SessionFilesSummary\n })\n\n// Move session to another project\nexport const moveSession = (\n sourceProject: string,\n sessionId: string,\n targetProject: string\n): Effect.Effect<MoveSessionResult, Error> =>\n Effect.gen(function* () {\n const sessionsDir = getSessionsDir()\n const sourcePath = path.join(sessionsDir, sourceProject)\n const targetPath = path.join(sessionsDir, targetProject)\n\n const sourceFile = path.join(sourcePath, `${sessionId}.jsonl`)\n const targetFile = path.join(targetPath, `${sessionId}.jsonl`)\n\n // Check source file exists\n const sourceExists = yield* Effect.tryPromise(() =>\n fs\n .access(sourceFile)\n .then(() => true)\n .catch(() => false)\n )\n\n if (!sourceExists) {\n return { success: false, error: 'Source session not found' }\n }\n\n // Check target file does not exist\n const targetExists = yield* Effect.tryPromise(() =>\n fs\n .access(targetFile)\n .then(() => true)\n .catch(() => false)\n )\n\n if (targetExists) {\n return { success: false, error: 'Session already exists in target project' }\n }\n\n // Create target directory if needed\n yield* Effect.tryPromise(() => fs.mkdir(targetPath, { recursive: true }))\n\n // Find linked agents before moving\n const linkedAgents = yield* findLinkedAgents(sourceProject, sessionId)\n\n // Move session file\n yield* Effect.tryPromise(() => fs.rename(sourceFile, targetFile))\n\n // Move linked agent files\n for (const agentId of linkedAgents) {\n const sourceAgentFile = path.join(sourcePath, `${agentId}.jsonl`)\n const targetAgentFile = path.join(targetPath, `${agentId}.jsonl`)\n\n const agentExists = yield* Effect.tryPromise(() =>\n fs\n .access(sourceAgentFile)\n .then(() => true)\n .catch(() => false)\n )\n\n if (agentExists) {\n yield* Effect.tryPromise(() => fs.rename(sourceAgentFile, targetAgentFile))\n }\n }\n\n return { success: true }\n })\n\n// Split session at a specific message\nexport const splitSession = (projectName: string, sessionId: string, splitAtMessageUuid: string) =>\n Effect.gen(function* () {\n const projectPath = path.join(getSessionsDir(), projectName)\n const filePath = path.join(projectPath, `${sessionId}.jsonl`)\n const content = yield* Effect.tryPromise(() => fs.readFile(filePath, 'utf-8'))\n const lines = content.trim().split('\\n').filter(Boolean)\n\n // Parse all messages preserving their full structure\n const allMessages = lines.map((line) => JSON.parse(line) as Record<string, unknown>)\n\n // Find the split point\n const splitIndex = allMessages.findIndex((m) => m.uuid === splitAtMessageUuid)\n if (splitIndex === -1) {\n return { success: false, error: 'Message not found' } satisfies SplitSessionResult\n }\n\n if (splitIndex === 0) {\n return { success: false, error: 'Cannot split at first message' } satisfies SplitSessionResult\n }\n\n // Generate new session ID\n const newSessionId = crypto.randomUUID()\n\n // Check if the split message is a continuation summary\n const splitMessage = allMessages[splitIndex]\n const shouldDuplicate = isContinuationSummary(splitMessage)\n\n // Split messages - if continuation summary, include it in both sessions\n let remainingMessages: Record<string, unknown>[]\n const movedMessages = allMessages.slice(splitIndex)\n\n if (shouldDuplicate) {\n // Create a copy of the continuation message with new UUID for the original session\n const duplicatedMessage: Record<string, unknown> = {\n ...splitMessage,\n uuid: crypto.randomUUID(),\n sessionId: sessionId, // Keep original session ID\n }\n remainingMessages = [...allMessages.slice(0, splitIndex), duplicatedMessage]\n } else {\n remainingMessages = allMessages.slice(0, splitIndex)\n }\n\n // Update moved messages with new sessionId and fix first message's parentUuid\n const updatedMovedMessages = movedMessages.map((msg, index) => {\n const updated: Record<string, unknown> = { ...msg, sessionId: newSessionId }\n if (index === 0) {\n // First message of new session should have no parent\n updated.parentUuid = null\n }\n return updated\n })\n\n // Write remaining messages to original file\n const remainingContent = remainingMessages.map((m) => JSON.stringify(m)).join('\\n') + '\\n'\n yield* Effect.tryPromise(() => fs.writeFile(filePath, remainingContent, 'utf-8'))\n\n // Write moved messages to new session file\n const newFilePath = path.join(projectPath, `${newSessionId}.jsonl`)\n const newContent = updatedMovedMessages.map((m) => JSON.stringify(m)).join('\\n') + '\\n'\n yield* Effect.tryPromise(() => fs.writeFile(newFilePath, newContent, 'utf-8'))\n\n // Update linked agent files that reference the old sessionId\n const agentFiles = yield* Effect.tryPromise(() => fs.readdir(projectPath))\n const agentJsonlFiles = agentFiles.filter((f) => f.startsWith('agent-') && f.endsWith('.jsonl'))\n\n for (const agentFile of agentJsonlFiles) {\n const agentPath = path.join(projectPath, agentFile)\n const agentContent = yield* Effect.tryPromise(() => fs.readFile(agentPath, 'utf-8'))\n const agentLines = agentContent.trim().split('\\n').filter(Boolean)\n\n if (agentLines.length === 0) continue\n\n const firstAgentMsg = JSON.parse(agentLines[0]) as { sessionId?: string }\n\n // If this agent belongs to the original session, check if it should be moved\n if (firstAgentMsg.sessionId === sessionId) {\n // Check if any message in moved messages is related to this agent\n const agentId = agentFile.replace('agent-', '').replace('.jsonl', '')\n const isRelatedToMoved = movedMessages.some(\n (msg) => (msg as { agentId?: string }).agentId === agentId\n )\n\n if (isRelatedToMoved) {\n // Update all messages in this agent file to reference new sessionId\n const updatedAgentMessages = agentLines.map((line) => {\n const msg = JSON.parse(line) as Record<string, unknown>\n return JSON.stringify({ ...msg, sessionId: newSessionId })\n })\n const updatedAgentContent = updatedAgentMessages.join('\\n') + '\\n'\n yield* Effect.tryPromise(() => fs.writeFile(agentPath, updatedAgentContent, 'utf-8'))\n }\n }\n }\n\n return {\n success: true,\n newSessionId,\n newSessionPath: newFilePath,\n movedMessageCount: movedMessages.length,\n duplicatedSummary: shouldDuplicate,\n } satisfies SplitSessionResult\n })\n\n// Remove invalid API key messages from a session, returns remaining message count\nconst cleanInvalidMessages = (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\n if (lines.length === 0) return { removedCount: 0, remainingCount: 0 }\n\n const messages = lines.map((line) => JSON.parse(line) as Message)\n const invalidIndices: number[] = []\n\n // Find all invalid API key messages\n messages.forEach((msg, idx) => {\n if (isInvalidApiKeyMessage(msg)) {\n invalidIndices.push(idx)\n }\n })\n\n if (invalidIndices.length === 0) {\n const userAssistantCount = messages.filter(\n (m) => m.type === 'user' || m.type === 'assistant'\n ).length\n const hasSummary = messages.some((m) => m.type === 'summary')\n // Count summary-only sessions as having 1 message\n const remainingCount = userAssistantCount > 0 ? userAssistantCount : hasSummary ? 1 : 0\n return { removedCount: 0, remainingCount }\n }\n\n // Remove invalid messages and fix parentUuid chain\n const filtered: Message[] = []\n let lastValidUuid: string | null = null\n\n for (let i = 0; i < messages.length; i++) {\n if (invalidIndices.includes(i)) {\n continue // Skip invalid message\n }\n\n const msg = messages[i]\n // Update parentUuid to point to last valid message\n if (msg.parentUuid && invalidIndices.some((idx) => messages[idx]?.uuid === msg.parentUuid)) {\n msg.parentUuid = lastValidUuid\n }\n filtered.push(msg)\n lastValidUuid = msg.uuid\n }\n\n const newContent =\n filtered.length > 0 ? filtered.map((m) => JSON.stringify(m)).join('\\n') + '\\n' : ''\n\n yield* Effect.tryPromise(() => fs.writeFile(filePath, newContent, 'utf-8'))\n\n const remainingUserAssistant = filtered.filter(\n (m) => m.type === 'user' || m.type === 'assistant'\n ).length\n const hasSummary = filtered.some((m) => m.type === 'summary')\n // Count summary-only sessions as having 1 message\n const remainingCount = remainingUserAssistant > 0 ? remainingUserAssistant : hasSummary ? 1 : 0\n return { removedCount: invalidIndices.length, remainingCount }\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 // Get orphan todos count (global, not per-project)\n const orphanTodos = yield* findOrphanTodos()\n const orphanTodoCount = orphanTodos.length\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 // Count empty sessions that have todos\n let emptyWithTodosCount = 0\n for (const session of emptySessions) {\n const linkedAgents = yield* findLinkedAgents(project.name, session.id)\n const hasTodos = yield* sessionHasTodos(session.id, linkedAgents)\n if (hasTodos) {\n emptyWithTodosCount++\n }\n }\n\n // Count orphan agents\n const orphanAgents = yield* findOrphanAgents(project.name)\n\n return {\n project: project.name,\n emptySessions,\n invalidSessions,\n emptyWithTodosCount,\n orphanAgentCount: orphanAgents.length,\n orphanTodoCount: 0, // Will set for first project only\n } satisfies CleanupPreview\n })\n ),\n { concurrency: 5 }\n )\n\n // Add orphanTodoCount only to the first result to avoid double counting\n if (results.length > 0) {\n results[0] = { ...results[0], orphanTodoCount }\n }\n\n return results\n })\n\n// Clear sessions\nexport const clearSessions = (options: {\n projectName?: string\n clearEmpty?: boolean\n clearInvalid?: boolean\n skipWithTodos?: boolean\n clearOrphanAgents?: boolean\n clearOrphanTodos?: boolean\n}) =>\n Effect.gen(function* () {\n const {\n projectName,\n clearEmpty = true,\n clearInvalid = true,\n skipWithTodos = true,\n clearOrphanAgents = false,\n clearOrphanTodos = false,\n } = options\n const projects = yield* listProjects\n const targetProjects = projectName ? projects.filter((p) => p.name === projectName) : projects\n\n let deletedSessionCount = 0\n let removedMessageCount = 0\n let deletedOrphanAgentCount = 0\n let deletedOrphanTodoCount = 0\n const sessionsToDelete: { project: string; sessionId: string }[] = []\n\n // Step 1: Clean invalid API key messages from all sessions (if clearInvalid)\n if (clearInvalid) {\n for (const project of targetProjects) {\n const projectPath = path.join(getSessionsDir(), project.name)\n const files = yield* Effect.tryPromise(() => fs.readdir(projectPath))\n const sessionFiles = files.filter((f) => f.endsWith('.jsonl') && !f.startsWith('agent-'))\n\n for (const file of sessionFiles) {\n const sessionId = file.replace('.jsonl', '')\n const result = yield* cleanInvalidMessages(project.name, sessionId)\n removedMessageCount += result.removedCount\n\n // Mark for deletion if now empty\n if (result.remainingCount === 0) {\n sessionsToDelete.push({ project: project.name, sessionId })\n }\n }\n }\n }\n\n // Step 2: Also find originally empty sessions (if clearEmpty is true)\n if (clearEmpty) {\n for (const project of targetProjects) {\n const sessions = yield* listSessions(project.name)\n for (const session of sessions) {\n if (session.messageCount === 0) {\n const alreadyMarked = sessionsToDelete.some(\n (s) => s.project === project.name && s.sessionId === session.id\n )\n if (!alreadyMarked) {\n // Skip sessions with todos if skipWithTodos is true\n if (skipWithTodos) {\n const linkedAgents = yield* findLinkedAgents(project.name, session.id)\n const hasTodos = yield* sessionHasTodos(session.id, linkedAgents)\n if (hasTodos) continue\n }\n sessionsToDelete.push({ project: project.name, sessionId: session.id })\n }\n }\n }\n }\n }\n\n // Step 3: Delete all empty sessions (this also deletes linked agents and todos)\n for (const { project, sessionId } of sessionsToDelete) {\n yield* deleteSession(project, sessionId)\n deletedSessionCount++\n }\n\n // Step 4: Delete orphan agents if requested\n if (clearOrphanAgents) {\n for (const project of targetProjects) {\n const result = yield* deleteOrphanAgents(project.name)\n deletedOrphanAgentCount += result.count\n }\n }\n\n // Step 5: Delete orphan todos if requested (global, not per-project)\n if (clearOrphanTodos) {\n const result = yield* deleteOrphanTodos()\n deletedOrphanTodoCount = result.deletedCount\n }\n\n return {\n success: true,\n deletedCount: deletedSessionCount,\n removedMessageCount,\n deletedOrphanAgentCount,\n deletedOrphanTodoCount,\n } satisfies ClearSessionsResult\n })\n"],"mappings":"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","names":["path","Effect","fs","path","Effect","Effect","fs","path","Effect","stat","agentBackupDir","hasSummary","remainingCount"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@claude-sessions/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Core library for Claude Code session management",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "es6.kr <drumrobot43@gmail.com>",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/es6kr/claude-code-sessions",
|
|
11
|
+
"directory": "packages/core"
|
|
12
|
+
},
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"import": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"effect": "^3.12.0"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/node": "^22.0.0",
|
|
27
|
+
"tsup": "^8.3.0",
|
|
28
|
+
"typescript": "^5.7.0"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsup",
|
|
32
|
+
"dev": "tsup --watch",
|
|
33
|
+
"typecheck": "tsc --noEmit",
|
|
34
|
+
"lint": "eslint src"
|
|
35
|
+
}
|
|
36
|
+
}
|